├── API.md ├── LICENSE ├── README.md ├── depends.txt ├── doc_addon.lua ├── init.lua ├── inventory_framework.lua ├── libs ├── cache.lua ├── crecipes.lua ├── filter.lua ├── maininv.lua ├── simple_po_reader.lua ├── smartfs-elements.lua └── smartfs.lua ├── locale ├── groups_de.po ├── groups_en.po └── groups_template.pot ├── mod.conf ├── pages ├── awards.lua ├── crafting.lua ├── creative.lua ├── doc.lua └── player.lua ├── screenshot.png ├── screenshot_crafting.png ├── screenshot_creative.png ├── screenshot_doc.png ├── screenshot_player.png ├── settingtypes.txt ├── textures ├── README ├── smart_inventory_background_border.png ├── smart_inventory_compress_button.png ├── smart_inventory_craftable_button.png ├── smart_inventory_crafting_inventory_button.png ├── smart_inventory_creative_button.png ├── smart_inventory_exit_button.png ├── smart_inventory_furnace.png ├── smart_inventory_get1_button.png ├── smart_inventory_get2_button.png ├── smart_inventory_get3_button.png ├── smart_inventory_left_arrow.png ├── smart_inventory_lookup_field.png ├── smart_inventory_preview_to_crafting_field.png ├── smart_inventory_reveal_tips_button.png ├── smart_inventory_right_arrow.png ├── smart_inventory_save1_button.png ├── smart_inventory_save2_button.png ├── smart_inventory_save3_button.png ├── smart_inventory_swapline_button.png ├── smart_inventory_sweep_button.png ├── smart_inventory_trash.png ├── smart_inventory_trash_all_button.png ├── smart_inventory_workbench_front.png ├── smart_inventory_workbench_sides.png └── smart_inventory_workbench_top.png ├── ui_tools.lua └── workbench.lua /API.md: -------------------------------------------------------------------------------- 1 | # API definition to working together with smart_inventory 2 | 3 | ## Pages framework 4 | 5 | ### Register page 6 | To get own page in smart_inventory the next register method should be used 7 | ``` 8 | smart_inventory.register_page({ 9 | name = string, 10 | icon = string, 11 | label = string, 12 | tooltip = string, 13 | smartfs_callback = function, 14 | sequence = number, 15 | on_button_click = function, 16 | is_visible_func = function, 17 | }) 18 | ``` 19 | - name - unique short name, used for identification 20 | - icon - Image displayed on page button. Optional 21 | - label - Label displayed on page button. Optional 22 | - tooltip - Text displayed at mouseover on the page button. Optional 23 | - smartfs_callback(state) - smartfs callback function See [smartfs documentation](https://github.com/minetest-mods/smartfs/blob/master/docs) and existing pages implementations for reference. 24 | - sequence - The buttons are sorted by this number (crafting=10, creative=15, player=20) 25 | - on_button_click(state) - function called each page button click 26 | - is_visible_func(state) - function for dynamic page enabelling. Should return bool value. 27 | 28 | ### Get the definition for registered smart_inventory page 29 | ```smart_inventory.get_registered_page(pagename)``` 30 | 31 | ### Get smartfs state for players inventory 32 | ```smart_inventory.get_player_state(playername)``` 33 | Get the root smartfs state for players inventory. Note: In workbench mode the function return nil if the player does not have the form open 34 | 35 | ### Get smartfs state for a registered page in players inventory 36 | ```smart_inventory.get_page_state(pagename, playername)``` 37 | 38 | ## Filter framework 39 | Smart_inventory uses a filter-framework for dynamic grouping in creative and crafting page. The filter framework allow to register additional classify filters for beter dynamic grouping results. 40 | Maybe the framework will be moved to own mod in the feature if needed. Please note the smart_inventory caches all results at init time so static groups only allowed. The groups will not be re-checked at runtime. 41 | 42 | ### Register new filter 43 | ``` 44 | smart_inventory.filter.register_filter({ 45 | name = string, 46 | check_item_by_def = function, 47 | get_description = function, 48 | get_keyword = function, 49 | is_valid = function, 50 | }) 51 | ``` 52 | - name - unique filter name 53 | - check_item_by_def(fltobj, itemdef) - function to check the item classify by item definition. Item definition is the reference to minetest.registered_items[item] entry 54 | next return values allowed: 55 | - true -> direct (belongs to) assignment to the classify group named by filtername 56 | - string -> dimension, steps splitted by ":" (`a:b:c:d results in filtername, filtername:a, filtername:a:b, filtername:a:b:c, filtername:a:b:c:d`) 57 | - key/value table -> multiple groups assignment. Values could be dimensions as above (`{a,b} results in filtername, filtername:a, filtername:b`) 58 | - nil -> no group assingment by this filter 59 | - get_description(fltobj, group) - optional - get human readable description for the dimension string (`filtername:a:b:c`) 60 | - get_keyword(fltobj, group) - get string that should be used for searches or nil if the group should not used in for searches 61 | - is_valid(fltobj, groupname) - Check if the groupname is valid. By default all groups are valid 62 | 63 | 64 | ### Filter Object methods 65 | 66 | smart_inventory.filter.get(name) get filter object by registered name. Returns filter object fltobj 67 | - fltobj:check_item_by_name(itemname) classify by itemname (wrapper for check_item_by_def) 68 | - fltobj:check_item_by_def(def) classify by item definition 69 | - fltobj:get_description(group) get group description 70 | - fltobj:get_keyword(group) get string that should be used for searches 71 | 72 | ## Cache framework 73 | cache.register_on_cache_filled(function, parameter) - hook to call additional initializations after the cache is filled 74 | 75 | 76 | ## Crecipes framework 77 | The should be used trough cache.register_on_cache_filled to be sure all items are already known 78 | crecipes.add_recipes_from_list(recipeslist) - Add Custom-Type Recipes to the smart inventory database 79 | 80 | ## Example usage for cache and crecipe 81 | ``` 82 | if minetest.global_exists("smart_inventory") then 83 | -- add grinder recipes to smart inventory database 84 | local crecipes = smart_inventory.crecipes 85 | local cache = smart_inventory.cache 86 | local function fill_citem_recipes() 87 | local recipelist = {} 88 | for _, e in ipairs(crushingfurnace_receipes) do 89 | table.insert(recipelist, { 90 | output = e[2], 91 | items = {e[1]}, 92 | type = "grinding" 93 | }) 94 | end 95 | crecipes.add_recipes_from_list(recipelist) 96 | end 97 | cache.register_on_cache_filled(fill_citem_recipes) 98 | end 99 | ``` 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smart_inventory 2 | 3 | ## Overview 4 | A fast Minetest inventory with focus on a great number of items and big screens. The special feature of this inventory is the dynamic classification filters that allow fast searching and browsing trough available items and show relevant invormations only to the user. 5 | 6 | The mod is organized in multiple pages, each page does have own focus and follow own vision. 7 | 8 | ## Crafting page 9 | ![Screenshot](https://github.com/bell07/minetest-smart_inventory/blob/master/screenshot_crafting.png) 10 | The vision is to not affect the gameplay trough crafting helpers. The dynamic search helper display currently relevant craft recipes only based on inventory content by default. 11 | - Contains the usual player-, and crafting inventory 12 | - Additional view of "craftable items" based on players inventory content 13 | - Dynamic grouping of craftable items for better overview 14 | - Lookup field to get all recipes with item in it - with filter for revealed items if the doc system is used 15 | - Search field - with filter for revealed items if the doc system is used 16 | - Compress - use the stack max size in inventory 17 | - Sweep - move content of crafting inventory back to the main inventory 18 | 19 | ### Optional support for other mods 20 | doc_items - if the doc system is found the crafting page shows only items craftable by known (revealed) items. 21 | A lookup button is available on already known items to jump to the documntation entry 22 | 23 | 24 | ## Creative page 25 | ![Screenshot](https://github.com/bell07/minetest-smart_inventory/blob/master/screenshot_creative.png) 26 | The vision is to get items fast searchable and gettable 27 | - 3 dynamic filters + text search field for fast items search 28 | - Sort out "mass"-groups to a special "Shaped" group 29 | - just click to the item to get it in inventory 30 | - cleanup of inventory trough "Trash" field 31 | - clean whole inventory trough "Trash all" button 32 | - save and restore inventory content in 3x save slots 33 | 34 | ## Player page 35 | ![Screenshot](https://github.com/bell07/minetest-smart_inventory/blob/master/screenshot_player.png) 36 | The vision is to get all skins and player customizations visual exposed. 37 | 38 | ### 3d_armor / clothing 39 | In creative mode all useable armor and clothing items available. The players inventory is not used in this mode. In survival only the armor and clothing from players inventory 40 | is shown. 41 | 42 | ### skins 43 | tested only with my fork https://github.com/bell07/minetest-skinsdb 44 | But it should be work with any fork that uses skins.skins[] and have *_preview.png files 45 | 46 | ## Doc page 47 | ![Screenshot](https://github.com/bell07/minetest-smart_inventory/blob/master/screenshot_doc.png) 48 | The vision is to get all ingame documentation available in a fast way. So navigation from crafting page is possible directly to the doc_item entry 49 | The doc and doc_items mods required to get the page 50 | 51 | 52 | ## Dependencies: 53 | Screen size at least 1024x768 / big screen. On my mobile with "full HD" it does not work. 54 | Minetest stable 0.4.15 or newer 55 | default mod (some graphics are used from this mod) 56 | 57 | 58 | 59 | ## Settings 60 | ``` 61 | #If enabled, the mod will show alternative human readable filterstrings if available. 62 | smart_inventory_friendly_group_names (Show “friendly” filter grouping names) bool true 63 | 64 | #List of groups defined for special handling of "Shaped nodes" (Comma separated). 65 | #Items in this groups ignores the "not_in_inventory" group and are moved to separate "Shaped" category 66 | smart_inventory_shaped_groups (List of groups to be handled as separate) string carpet,door,fence,stair,slab,wall,micro,panel,slope,dye 67 | 68 | #If enabled, the the mod does not replace other inventory mods. 69 | #The functionality is provided in a workbench. 70 | smart_inventory_workbench_mode (Use workbench instead of players inventory) bool false 71 | ``` 72 | 73 | License: [LGPL-3](https://github.com/bell07/minetest-smart_inventory/blob/master/LICENSE) 74 | Textures: 75 | - Workbench: WTFPL (credits: to xdecor project) 76 | - Buttons: WTFPL (credits to Stix (Minetest-forum)) 77 | - Arrow buttons: WTFPL (credits to daretmavi) 78 | -------------------------------------------------------------------------------- /depends.txt: -------------------------------------------------------------------------------- 1 | creative? 2 | sfinv? 3 | 3d_armor? 4 | skinsdb? 5 | doc_items? 6 | unifieddyes? 7 | -------------------------------------------------------------------------------- /doc_addon.lua: -------------------------------------------------------------------------------- 1 | smart_inventory.doc_items_mod = minetest.get_modpath("doc_items") 2 | 3 | local doc_addon = {} 4 | 5 | local doc_item_entries = {} 6 | 7 | function doc_addon.is_revealed_item(itemname, playername) 8 | if not smart_inventory.doc_items_mod then 9 | return true 10 | end 11 | 12 | doc_addon.get_category_list() 13 | itemname = minetest.registered_aliases[itemname] or itemname 14 | local doc_entry = doc_item_entries[itemname] 15 | if doc_entry then 16 | return doc.entry_revealed(playername, doc_entry.cid, doc_entry.eid) 17 | else 18 | -- unknown item 19 | return false 20 | end 21 | return true 22 | end 23 | 24 | function doc_addon.show(itemname, playername) 25 | if not smart_inventory.doc_items_mod then 26 | return 27 | end 28 | 29 | doc_addon.get_category_list() 30 | itemname = minetest.registered_aliases[itemname] or itemname 31 | local doc_entry = doc_item_entries[itemname] 32 | if doc_entry then 33 | doc.mark_entry_as_viewed(playername, doc_entry.cid, doc_entry.eid) 34 | local state = smart_inventory.get_page_state("doc", playername) 35 | local codebox = state:get("code") 36 | codebox.edata = doc_entry 37 | doc.data.players[playername].galidx = 1 38 | codebox:submit() --update the page 39 | state.location.parentState:get("doc_button"):submit() -- switch to the tab 40 | end 41 | 42 | end 43 | 44 | -- Get sorted category list 45 | local doc_category_list = nil 46 | 47 | function doc_addon.get_category_list() 48 | -- build on first usage 49 | if not doc_category_list and smart_inventory.doc_items_mod then 50 | doc_category_list = {} 51 | for _, category_name in ipairs(doc.data.category_order) do 52 | local category = doc.data.categories[category_name] 53 | if category then 54 | local entries_data = {} 55 | for _, eid in ipairs(doc.get_sorted_entry_names(category_name)) do 56 | local entry = doc.data.categories[category_name].entries[eid] 57 | if entry.data.itemstring and 58 | minetest.registered_items[entry.data.itemstring] and 59 | (entry.data.def == minetest.registered_items[entry.data.itemstring] or entry.data.def.door) then 60 | local edata = {cid = category_name, cid_data = category, eid = eid, data = entry} 61 | table.insert(entries_data, edata) 62 | doc_item_entries[entry.data.itemstring] = edata 63 | end 64 | end 65 | table.insert(doc_category_list, {cid = category_name, data = category, entries = entries_data}) 66 | end 67 | end 68 | end 69 | return doc_category_list 70 | end 71 | 72 | ------------------------- 73 | return doc_addon 74 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | smart_inventory = {} 2 | smart_inventory.modpath = minetest.get_modpath(minetest.get_current_modname()) 3 | local modpath = smart_inventory.modpath 4 | 5 | -- load libs 6 | smart_inventory.txt = dofile(modpath.."/libs/simple_po_reader.lua") 7 | smart_inventory.smartfs = dofile(modpath.."/libs/smartfs.lua") 8 | smart_inventory.smartfs_elements = dofile(modpath.."/libs/smartfs-elements.lua") 9 | 10 | smart_inventory.doc_addon = dofile(modpath.."/doc_addon.lua") 11 | 12 | smart_inventory.filter = dofile(modpath.."/libs/filter.lua") 13 | smart_inventory.cache = dofile(modpath.."/libs/cache.lua") 14 | smart_inventory.crecipes = dofile(modpath.."/libs/crecipes.lua") 15 | smart_inventory.maininv = dofile(modpath.."/libs/maininv.lua") 16 | 17 | smart_inventory.ui_tools = dofile(modpath.."/ui_tools.lua") 18 | -- register pages 19 | dofile(modpath.."/inventory_framework.lua") 20 | dofile(modpath.."/pages/crafting.lua") 21 | dofile(modpath.."/pages/creative.lua") 22 | dofile(modpath.."/pages/player.lua") 23 | dofile(modpath.."/pages/doc.lua") 24 | dofile(modpath.."/pages/awards.lua") 25 | 26 | -- Cleanup inventories 27 | minetest.register_on_leaveplayer(function(player) 28 | local player_name = player:get_player_name() 29 | minetest.remove_detached_inventory(player_name.."_crafting_inv") 30 | minetest.remove_detached_inventory(player_name.."_trash_inv") 31 | end) 32 | -------------------------------------------------------------------------------- /inventory_framework.lua: -------------------------------------------------------------------------------- 1 | local smartfs = smart_inventory.smartfs 2 | local maininv = smart_inventory.maininv 3 | local modpath = smart_inventory.modpath 4 | 5 | -- smartfs callback 6 | local inventory_form = smartfs.create("smart_inventory:main", function(state) 7 | 8 | -- enhanced object to the main inventory functions 9 | state.param.invobj = maininv.get(state.location.player) 10 | 11 | -- Set language code 12 | local player_info = minetest.get_player_information(state.location.player) 13 | if player_info and player_info.lang_code ~= "" then 14 | state.lang_code = player_info.lang_code 15 | end 16 | 17 | -- tabbed view controller 18 | local tab_controller = { 19 | _tabs = {}, 20 | active_name = nil, 21 | set_active = function(self, tabname) 22 | for name, def in pairs(self._tabs) do 23 | if name == tabname then 24 | def.button:setBackground("halo.png") 25 | def.view:setVisible(true) 26 | else 27 | def.button:setBackground(nil) 28 | def.view:setVisible(false) 29 | end 30 | end 31 | self.active_name = tabname 32 | end, 33 | tab_add = function(self, name, def) 34 | def.viewstate:size(20,10) --size of tab view 35 | self._tabs[name] = def 36 | end, 37 | get_active_name = function(self) 38 | return self.active_name 39 | end, 40 | } 41 | 42 | --set screen size 43 | state:size(20,12) 44 | state:label(1,0.2,"header","Smart Inventory") 45 | state:image(0,0,1,1,"header_logo", "logo.png") 46 | state:image_button(19,0,1,1,"exit", "","smart_inventory_exit_button.png", true):setTooltip("Close the inventory") 47 | local button_x = 0.1 48 | table.sort(smart_inventory.registered_pages, function(a,b) 49 | if not a.sequence then 50 | return false 51 | elseif not b.sequence then 52 | return true 53 | elseif a.sequence > b.sequence then 54 | return false 55 | else 56 | return true 57 | end 58 | end) 59 | for _, def in ipairs(smart_inventory.registered_pages) do 60 | assert(def.smartfs_callback, "Callback function needed") 61 | assert(def.name, "Name is needed") 62 | if not def.is_visible_func or def.is_visible_func(state) then 63 | local tabdef = {} 64 | local label 65 | if not def.label then 66 | label = "" 67 | else 68 | label = def.label 69 | end 70 | tabdef.button = state:button(button_x,11.2,1,1,def.name.."_button",label) 71 | if def.icon then 72 | tabdef.button:setImage(def.icon) 73 | end 74 | tabdef.button:setTooltip(def.tooltip) 75 | tabdef.button:onClick(function(self) 76 | tab_controller:set_active(def.name) 77 | if def.on_button_click then 78 | def.on_button_click(tabdef.viewstate) 79 | end 80 | end) 81 | tabdef.view = state:container(0,1,def.name.."_container") 82 | tabdef.viewstate = tabdef.view:getContainerState() 83 | def.smartfs_callback(tabdef.viewstate) 84 | tab_controller:tab_add(def.name, tabdef) 85 | button_x = button_x + 1 86 | end 87 | end 88 | tab_controller:set_active(smart_inventory.registered_pages[1].name) 89 | end) 90 | 91 | if minetest.settings:get_bool("smart_inventory_workbench_mode") then 92 | dofile(modpath.."/workbench.lua") 93 | smart_inventory.get_player_state = function(playername) 94 | -- check the inventory is shown 95 | local state = smartfs.opened[playername] 96 | if state and (not state.obsolete) and 97 | state.location.type == "player" and 98 | state.def.name == "smart_inventory:main" then 99 | return state 100 | end 101 | end 102 | else 103 | smartfs.set_player_inventory(inventory_form) 104 | smart_inventory.get_player_state = function(playername) 105 | return smartfs.inv[playername] 106 | end 107 | end 108 | 109 | -- pages list 110 | smart_inventory.registered_pages = {} 111 | 112 | -- add new page 113 | function smart_inventory.register_page(def) 114 | table.insert(smart_inventory.registered_pages, def) 115 | end 116 | 117 | -- smart_inventory.get_player_state(playername) defined above 118 | 119 | -- get state of active page 120 | function smart_inventory.get_page_state(pagename, playername) 121 | local rootstate = smart_inventory.get_player_state(playername) 122 | if not rootstate then 123 | return 124 | end 125 | local view = rootstate:get(pagename.."_container") 126 | if not view then 127 | return 128 | end 129 | return view:getContainerState() 130 | end 131 | 132 | -- get definition of registered page 133 | function smart_inventory.get_registered_page(pagename) 134 | for _, registred_page in ipairs(smart_inventory.registered_pages) do 135 | if registred_page.name == pagename then 136 | return registred_page 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /libs/cache.lua: -------------------------------------------------------------------------------- 1 | local filter = smart_inventory.filter 2 | 3 | local cache = {} 4 | cache.cgroups = {} -- cache groups 5 | cache.itemgroups = {} -- raw item groups for recipe checks 6 | cache.citems = {} 7 | 8 | ----------------------------------------------------- 9 | -- Add an Item to the cache 10 | ----------------------------------------------------- 11 | function cache.add_item(item_def) 12 | 13 | -- already in cache. Skip duplicate processing 14 | if cache.citems[item_def.name] then 15 | return 16 | end 17 | 18 | -- fill raw groups cache for recipes 19 | for group, value in pairs(item_def.groups) do 20 | cache.itemgroups[group] = cache.itemgroups[group] or {} 21 | cache.itemgroups[group][item_def.name] = item_def 22 | end 23 | 24 | local entry = { 25 | name = item_def.name, 26 | in_output_recipe = {}, 27 | in_craft_recipe = {}, 28 | cgroups = {} 29 | } 30 | cache.citems[item_def.name] = entry 31 | -- classify the item 32 | for _, flt in pairs(filter.registered_filter) do 33 | local filter_result = flt:check_item_by_def(item_def) 34 | if filter_result then 35 | if filter_result == true then 36 | cache.assign_to_group(flt.name, item_def, flt) 37 | else 38 | if type(filter_result) ~= "table" then 39 | if tonumber(filter_result) ~= nil then 40 | filter_result = {[flt.name..":"..filter_result] = true} 41 | else 42 | filter_result = {[filter_result] = true} 43 | end 44 | end 45 | for key, val in pairs(filter_result) do 46 | local filter_entry = tostring(key) 47 | if val ~= true then 48 | filter_entry = filter_entry..":"..tostring(val) 49 | end 50 | cache.assign_to_group(filter_entry, item_def, flt) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | 58 | ----------------------------------------------------- 59 | -- Add a item to cache group 60 | ----------------------------------------------------- 61 | function cache.assign_to_group(group_name, itemdef, flt) 62 | 63 | -- check and build filter chain 64 | local abs_group 65 | local parent_ref 66 | local parent_stringpos 67 | 68 | for rel_group in group_name:gmatch("[^:]+") do 69 | -- get parent relation and absolute path 70 | if abs_group then 71 | parent_ref = cache.cgroups[abs_group] 72 | parent_stringpos = string.len(abs_group)+2 73 | abs_group = abs_group..":"..rel_group 74 | else 75 | abs_group = rel_group 76 | end 77 | if flt:is_valid(abs_group) then 78 | -- check if group is new, create it 79 | if not cache.cgroups[abs_group] then 80 | if parent_ref then 81 | parent_ref.childs[abs_group] = string.sub(group_name, parent_stringpos) 82 | end 83 | local group = { 84 | name = abs_group, 85 | items = {}, 86 | parent = parent_ref, 87 | childs = {}, 88 | } 89 | group.group_desc = flt:get_description(group) 90 | group.keyword = flt:get_keyword(group) 91 | cache.cgroups[abs_group] = group 92 | end 93 | 94 | -- set relation 95 | cache.cgroups[abs_group].items[itemdef.name] = itemdef 96 | cache.citems[itemdef.name].cgroups[abs_group] = cache.cgroups[abs_group] 97 | end 98 | end 99 | end 100 | 101 | ----------------------------------------------------- 102 | -- Hook / Event for further initializations of the cache is filled 103 | ----------------------------------------------------- 104 | cache.registered_on_cache_filled = {} 105 | function cache.register_on_cache_filled(func, ...) 106 | assert(type(func) == "function", "register_on_cache_filled needs a function") 107 | table.insert(cache.registered_on_cache_filled, { func = func, opt = {...}}) 108 | end 109 | 110 | local function process_on_cache_filled() 111 | for _, hook in ipairs(cache.registered_on_cache_filled) do 112 | hook.func(unpack(hook.opt)) 113 | end 114 | end 115 | 116 | 117 | ----------------------------------------------------- 118 | -- Fill the cache at init 119 | ----------------------------------------------------- 120 | local function fill_cache() 121 | local shape_filter = filter.get("shape") 122 | for _, def in pairs(minetest.registered_items) do 123 | 124 | -- build groups and items cache 125 | if def.description and def.description ~= "" and 126 | (not def.groups.not_in_creative_inventory or shape_filter:check_item_by_def(def)) then 127 | cache.add_item(def) 128 | end 129 | end 130 | 131 | -- call hooks 132 | minetest.after(0, process_on_cache_filled) 133 | end 134 | minetest.after(0, fill_cache) 135 | 136 | ----------------------------------------------------- 137 | -- return the reference to the mod 138 | ----------------------------------------------------- 139 | return cache 140 | -------------------------------------------------------------------------------- /libs/crecipes.lua: -------------------------------------------------------------------------------- 1 | local doc_addon = smart_inventory.doc_addon 2 | local cache = smart_inventory.cache 3 | local filter = smart_inventory.filter 4 | 5 | local crecipes = {} 6 | crecipes.crecipes = {} --list of all recipes 7 | 8 | ----------------------------------------------------- 9 | -- crecipe: Class 10 | ----------------------------------------------------- 11 | local crecipe_class = {} 12 | local crecipe_class_mt = {__index = crecipe_class} 13 | crecipes.crecipe_class = crecipe_class 14 | 15 | ----------------------------------------------------- 16 | -- crecipes: analyze all data. Return false if invalid recipe. true on success 17 | ----------------------------------------------------- 18 | function crecipe_class:analyze() 19 | -- check recipe output 20 | 21 | if self._recipe.type == "cooking" then 22 | return false --fuel not supported 23 | end 24 | 25 | if self._recipe.output == "" then 26 | minetest.log("[smartfs_inventory] broken recipe without output "..dump(self._recipe)) 27 | return false 28 | end 29 | 30 | local outstack = ItemStack(self._recipe.output) 31 | if outstack:get_meta():get_int("palette_index") > 0 then 32 | minetest.log("verbose", "[smartfs_inventory] ignore unifieddyes recipe "..self._recipe.output) 33 | return -- not supported 34 | end 35 | 36 | self.out_item = outstack:get_definition() 37 | 38 | if not self.out_item or not self.out_item.name then 39 | minetest.log("[smartfs_inventory] unknown recipe result "..self._recipe.output) 40 | return false 41 | end 42 | 43 | -- check recipe items/groups 44 | for _, recipe_item in pairs(self._recipe.items) do 45 | if recipe_item ~= "" then 46 | if self._items[recipe_item] then 47 | self._items[recipe_item].count = self._items[recipe_item].count + 1 48 | else 49 | self._items[recipe_item] = {count = 1} 50 | end 51 | end 52 | end 53 | for recipe_item, iteminfo in pairs(self._items) do 54 | if recipe_item:sub(1, 6) ~= "group:" then 55 | local itemname = minetest.registered_aliases[recipe_item] or recipe_item 56 | if minetest.registered_items[itemname] then 57 | iteminfo.items = {[itemname] = minetest.registered_items[itemname]} 58 | else 59 | minetest.log("[smartfs_inventory] unknown item in recipe: "..itemname.." for result "..self.out_item.name) 60 | return false 61 | end 62 | else 63 | local retitems 64 | for groupname in string.gmatch(recipe_item:sub(7), '([^,]+)') do 65 | if not retitems then --first entry 66 | if cache.itemgroups[groupname] then 67 | retitems = {} 68 | for k,v in pairs(cache.itemgroups[groupname]) do 69 | retitems[k] = v 70 | end 71 | else 72 | minetest.log("[smartfs_inventory] unknown group description in recipe: "..recipe_item.." / "..groupname.." for result "..self.out_item.name) 73 | return false 74 | end 75 | else 76 | for itemname, itemdef in pairs(retitems) do 77 | if not minetest.registered_items[itemname].groups[groupname] then 78 | retitems[itemname] = nil 79 | end 80 | end 81 | end 82 | if not retitems or not next(retitems) then 83 | minetest.log("[smartfs_inventory] no items matches group: "..recipe_item.." for result "..self.out_item.name) 84 | return false 85 | end 86 | end 87 | iteminfo.items = retitems 88 | end 89 | end 90 | 91 | -- invalid recipe 92 | if not self._items then 93 | minetest.log("[smartfs_inventory] skip recipe for: "..recipe_item) 94 | return false 95 | else 96 | return true 97 | end 98 | end 99 | 100 | ----------------------------------------------------- 101 | -- crecipes: Check if the recipe is revealed to the player 102 | ----------------------------------------------------- 103 | function crecipe_class:is_revealed(playername, recursiv_checked_items) 104 | local recipe_valid = true 105 | for _, entry in pairs(self._items) do 106 | recipe_valid = false 107 | for _, itemdef in pairs(entry.items) do 108 | if doc_addon.is_revealed_item(itemdef.name, playername) then 109 | recipe_valid = true 110 | break 111 | end 112 | 113 | if cache.citems[itemdef.name].cgroups["shape"] then -- Check shapes recursive 114 | recursiv_checked_items = recursiv_checked_items or {} 115 | for _, recipe in ipairs(cache.citems[itemdef.name].in_output_recipe) do 116 | local crecipe = crecipes.crecipes[recipe] 117 | if recursiv_checked_items[crecipe.out_item.name] == nil then 118 | recursiv_checked_items[crecipe.out_item.name] = false --avoid re-recursion 119 | recursiv_checked_items[crecipe.out_item.name] = crecipe:is_revealed(playername, recursiv_checked_items) 120 | end 121 | if recursiv_checked_items[crecipe.out_item.name] == true then 122 | recipe_valid = true 123 | break 124 | end 125 | end 126 | if recipe_valid then 127 | break 128 | end 129 | end 130 | end 131 | if not recipe_valid then 132 | break 133 | end 134 | end 135 | return recipe_valid 136 | end 137 | 138 | ----------------------------------------------------- 139 | -- crecipes: Returns recipe without groups, with replacements 140 | ----------------------------------------------------- 141 | function crecipe_class:get_with_placeholder(playername, inventory_tab) 142 | local recipe = {} 143 | for k, v in pairs(self._recipe) do 144 | recipe[k] = v 145 | end 146 | recipe.items = {} 147 | for k, v in pairs(self._recipe.items) do 148 | recipe.items[k] = v 149 | end 150 | 151 | local recursiv_checked_items = {} 152 | if inventory_tab then 153 | for k, v in pairs(inventory_tab) do 154 | recursiv_checked_items[k] = v 155 | end 156 | end 157 | self:is_revealed(playername, recursiv_checked_items) -- enhance recursiv_checked_items 158 | 159 | for key, recipe_item in pairs(recipe.items) do 160 | local item 161 | 162 | -- Check for matching item in inventory and revealed cache 163 | if inventory_tab then 164 | local itemcount = 0 165 | for _, item_in_list in pairs(self._items[recipe_item].items) do 166 | local in_inventory = inventory_tab[item_in_list.name] 167 | if in_inventory == true then 168 | item = item_in_list.name 169 | break 170 | elseif in_inventory and in_inventory > itemcount then 171 | item = item_in_list.name 172 | itemcount = in_inventory 173 | end 174 | end 175 | end 176 | 177 | -- second try, revealed by recipe item 178 | if not item then 179 | for _, item_in_list in pairs(self._items[recipe_item].items) do 180 | if recursiv_checked_items[item_in_list.name] then 181 | item = item_in_list.name 182 | break 183 | end 184 | end 185 | end 186 | 187 | -- third try, get any revealed item 188 | if not item then 189 | for _, item_in_list in pairs(self._items[recipe_item].items) do 190 | if doc_addon.is_revealed_item(item_in_list.name, playername) then 191 | item = item_in_list.name 192 | break 193 | end 194 | end 195 | end 196 | 197 | -- last try, just get one item 198 | if not item and self._items[recipe_item].items[1] then 199 | item = self._items[recipe_item].items[1].name 200 | end 201 | 202 | -- set recipe item 203 | if item then 204 | if recipe_item ~= item then 205 | recipe.items[key] = { 206 | item = item, 207 | tooltip = recipe_item, 208 | text = 'G', 209 | } 210 | end 211 | end 212 | end 213 | return recipe 214 | end 215 | 216 | ----------------------------------------------------- 217 | -- crecipes: Check if recipe contains only items provided in reference_items 218 | ----------------------------------------------------- 219 | function crecipe_class:is_craftable_by_items(reference_items) 220 | local item_ok = false 221 | for _, entry in pairs(self._items) do 222 | item_ok = false 223 | for _, itemdef in pairs(entry.items) do 224 | if reference_items[itemdef.name] then 225 | item_ok = true 226 | end 227 | end 228 | if item_ok == false then 229 | break 230 | end 231 | end 232 | return item_ok 233 | end 234 | 235 | ----------------------------------------------------- 236 | -- crecipes: Check if the items placed in grid matches the recipe 237 | ----------------------------------------------------- 238 | function crecipe_class:check_craftable_by_grid(grid) 239 | -- only "normal" recipes supported 240 | if self.recipe_type ~= "normal" then 241 | return false 242 | end 243 | 244 | for i = 1, 9 do 245 | local grid_item = grid[i]:get_name() 246 | -- check only fields filled in crafting grid 247 | if grid_item and grid_item ~= "" then 248 | -- check if item defined in recipe at this place 249 | local item_ok = false 250 | local recipe_item 251 | -- default case - 3x3 crafting grid 252 | local width = self._recipe.width 253 | if not width or width == 0 or width == 3 then 254 | recipe_item = self._recipe.items[i] 255 | else 256 | -- complex case - recalculate to the 3x3 crafting grid 257 | local x = math.fmod((i-1),3)+1 258 | if x <= width then 259 | local y = math.floor((i-1)/3+1) 260 | local coord = (y-1)*width+x 261 | recipe_item = self._recipe.items[coord] 262 | else 263 | recipe_item = "" 264 | end 265 | end 266 | 267 | if not recipe_item or recipe_item == "" then 268 | return false 269 | end 270 | 271 | -- check if it is a compatible item 272 | for _, itemdef in pairs(self._items[recipe_item].items) do 273 | if itemdef.name == grid_item then 274 | item_ok = true 275 | break 276 | end 277 | end 278 | if not item_ok then 279 | return false 280 | end 281 | end 282 | end 283 | return true 284 | end 285 | 286 | ----------------------------------------------------- 287 | -- Recipe object Constructor 288 | ----------------------------------------------------- 289 | function crecipes.new(recipe) 290 | local self = setmetatable({}, crecipe_class_mt) 291 | -- self.out_item = nil 292 | self._recipe = recipe 293 | self.recipe_type = recipe.type 294 | self._items = {} 295 | return self 296 | end 297 | 298 | ----------------------------------------------------- 299 | -- Get all revealed recipes with at least one item in reference_items table 300 | ----------------------------------------------------- 301 | function crecipes.get_revealed_recipes_with_items(playername, reference_items) 302 | local recipelist = {} 303 | local revealed_items_cache = {} 304 | for itemname, _ in pairs(reference_items) do 305 | if cache.citems[itemname] and cache.citems[itemname].in_craft_recipe then 306 | for _, recipe in ipairs(cache.citems[itemname].in_craft_recipe) do 307 | local crecipe = crecipes.crecipes[recipe] 308 | if crecipe and crecipe:is_revealed(playername, revealed_items_cache) then 309 | recipelist[recipe] = crecipe 310 | end 311 | -- lookup one step forward for shapes 312 | if cache.citems[crecipe.out_item.name].cgroups["shape"] then 313 | for _, recipe2 in ipairs(cache.citems[crecipe.out_item.name].in_craft_recipe) do 314 | local crecipe = crecipes.crecipes[recipe2] 315 | if crecipe and crecipe:is_revealed(playername, revealed_items_cache) then 316 | recipelist[recipe2] = crecipe 317 | end 318 | end 319 | end 320 | end 321 | end 322 | if cache.citems[itemname] and cache.citems[itemname].in_output_recipe then 323 | for _, recipe in ipairs(cache.citems[itemname].in_output_recipe) do 324 | local crecipe = crecipes.crecipes[recipe] 325 | if crecipe and crecipe:is_revealed(playername, revealed_items_cache) then 326 | recipelist[recipe] = crecipe 327 | end 328 | end 329 | end 330 | end 331 | return recipelist 332 | end 333 | 334 | ----------------------------------------------------- 335 | -- Get all recipes with all required items in reference items 336 | ----------------------------------------------------- 337 | function crecipes.get_recipes_craftable(playername, reference_items) 338 | local all = crecipes.get_revealed_recipes_with_items(playername, reference_items) 339 | local craftable = {} 340 | for recipe, crecipe in pairs(all) do 341 | if crecipe:is_craftable_by_items(reference_items) then 342 | craftable[recipe] = crecipe 343 | end 344 | end 345 | return craftable 346 | end 347 | 348 | ----------------------------------------------------- 349 | -- Get all recipes that match to already placed items on crafting grid 350 | ----------------------------------------------------- 351 | function crecipes.get_recipes_started_craft(playername, grid, reference_items) 352 | local all = crecipes.get_revealed_recipes_with_items(playername, reference_items) 353 | local craftable = {} 354 | for recipe, crecipe in pairs(all) do 355 | if crecipe:check_craftable_by_grid(grid) then 356 | craftable[recipe] = crecipe 357 | end 358 | end 359 | return craftable 360 | end 361 | 362 | function crecipes.add_recipes_from_list(recipelist) 363 | if recipelist then 364 | for _, recipe in ipairs(recipelist) do 365 | local recipe_obj = crecipes.new(recipe) 366 | if recipe_obj:analyze() then 367 | -- probably hidden therefore not indexed previous. But Items with recipe should be allways visible 368 | cache.add_item(minetest.registered_items[recipe_obj.out_item.name]) 369 | table.insert(cache.citems[recipe_obj.out_item.name].in_output_recipe, recipe) 370 | crecipes.crecipes[recipe] = recipe_obj 371 | if recipe_obj.recipe_type ~= "normal" then 372 | cache.assign_to_group("recipetype:"..recipe_obj.recipe_type, recipe_obj.out_item, filter.get("recipetype")) 373 | end 374 | for _, entry in pairs(recipe_obj._items) do 375 | for itemname, itemdef in pairs(entry.items) do 376 | cache.add_item(itemdef) -- probably hidden therefore not indexed previous. But Items with recipe should be allways visible 377 | table.insert(cache.citems[itemdef.name].in_craft_recipe, recipe) 378 | cache.assign_to_group("ingredient:"..itemdef.name, recipe_obj.out_item, filter.get("ingredient")) 379 | end 380 | end 381 | end 382 | end 383 | end 384 | end 385 | 386 | ----------------------------------------------------- 387 | -- Fill the recipes cache at init 388 | ----------------------------------------------------- 389 | local function fill_recipe_cache() 390 | for itemname, _ in pairs(minetest.registered_items) do 391 | crecipes.add_recipes_from_list(minetest.get_all_craft_recipes(itemname)) 392 | end 393 | for itemname, _ in pairs(minetest.registered_aliases) do 394 | crecipes.add_recipes_from_list(minetest.get_all_craft_recipes(itemname)) 395 | end 396 | end 397 | -- register to process after cache is filled 398 | cache.register_on_cache_filled(fill_recipe_cache) 399 | 400 | return crecipes 401 | -------------------------------------------------------------------------------- /libs/filter.lua: -------------------------------------------------------------------------------- 1 | local txt = smart_inventory.txt 2 | 3 | -------------------------------------------------------------- 4 | -- Filter class 5 | -------------------------------------------------------------- 6 | local filter_class = {} 7 | local filter_class_mt = {__index = filter_class} 8 | 9 | function filter_class:check_item_by_name(itemname) 10 | if minetest.registered_items[itemname] then 11 | return self:check_item_by_def(minetest.registered_items[itemname]) 12 | end 13 | end 14 | 15 | function filter_class:check_item_by_def(def) 16 | error("check_item_by_def needs redefinition:"..debug.traceback()) 17 | end 18 | 19 | function filter_class:_get_description(group) 20 | if txt then 21 | if txt[group.name] then 22 | return txt[group.name].." ("..group.name..")" 23 | elseif group.parent and group.parent.childs[group.name] and txt[group.parent.name] then 24 | return txt[group.parent.name].." "..group.parent.childs[group.name].." ("..group.name..")" 25 | else 26 | return group.name 27 | end 28 | else 29 | return group.name 30 | end 31 | end 32 | filter_class.get_description = filter_class._get_description 33 | 34 | function filter_class:_get_keyword(group) 35 | return group.group_desc 36 | end 37 | 38 | filter_class.get_keyword = filter_class._get_keyword 39 | 40 | function filter_class:is_valid(group) 41 | return true 42 | end 43 | 44 | local filter = {} 45 | filter.registered_filter = {} 46 | 47 | function filter.get(name) 48 | return filter.registered_filter[name] 49 | end 50 | 51 | function filter.register_filter(def) 52 | assert(def.name, "filter needs a name") 53 | assert(def.check_item_by_def, "filter function check_item_by_def required") 54 | assert(not filter.registered_filter[def.name], "filter already exists") 55 | setmetatable(def, filter_class_mt) 56 | filter.registered_filter[def.name] = def 57 | end 58 | 59 | 60 | -- rename groups for beter consistency 61 | filter.group_rename = { 62 | customnode_default = "customnode", 63 | } 64 | 65 | -- group configurations per basename 66 | -- true means is dimension 67 | -- 1 means replace the base only ("food_choco_powder" => food:choco_powder") 68 | filter.base_group_config = { 69 | armor = true, 70 | physics = true, 71 | basecolor = true, 72 | excolor = true, 73 | color = true, 74 | unicolor = true, 75 | food = 1, 76 | customnode = true, 77 | } 78 | 79 | -- hide this groups 80 | filter.group_hide_config = { 81 | armor_count = true, 82 | not_in_creative_inventory = false, 83 | } 84 | 85 | -- value of this group will be recalculated to % 86 | filter.group_wear_config = { 87 | armor_use = true, 88 | } 89 | 90 | -- Ususally 1 means true for group values. This is an exceptions table for this rule 91 | filter.group_with_value_1_config = { 92 | oddly_breakable_by_hand = true, 93 | } 94 | 95 | -------------------------------------------------------------- 96 | -- Filter group 97 | -------------------------------------------------------------- 98 | filter.register_filter({ 99 | name = "group", 100 | check_item_by_def = function(self, def) 101 | local ret = {} 102 | for k_orig, v in pairs(def.groups) do 103 | local k = filter.group_rename[k_orig] or k_orig 104 | local mk, mv 105 | 106 | -- Check group base 107 | local basename 108 | for z in k:gmatch("[^_]+") do 109 | basename = z 110 | break 111 | end 112 | local basegroup_config = filter.base_group_config[basename] 113 | if basegroup_config == true then 114 | mk = string.gsub(k, "_", ":") 115 | elseif basegroup_config == 1 then 116 | mk = string.gsub(k, "^"..basename.."_", basename..":") 117 | else 118 | mk = k 119 | end 120 | 121 | -- stack wear related value 122 | if filter.group_wear_config[k] then 123 | mv = tostring(math.floor(v / 65535 * 10000 + 0.5)/100).." %" 124 | -- value-expandable groups 125 | elseif v ~= 1 or k == filter.group_with_value_1_config[k] then 126 | mv = v 127 | else 128 | mv = true 129 | end 130 | 131 | if v ~= 0 and mk and not filter.group_hide_config[k] then 132 | ret[mk] = mv 133 | end 134 | end 135 | return ret 136 | end, 137 | }) 138 | 139 | filter.register_filter({ 140 | name = "type", 141 | check_item_by_def = function(self, def) 142 | return self.name..":"..def.type 143 | end, 144 | get_keyword = function(self, group) 145 | if group.name ~= self.name then 146 | return group.parent.childs[group.name] 147 | end 148 | end 149 | }) 150 | 151 | filter.register_filter({ 152 | name = "mod", 153 | check_item_by_def = function(self, def) 154 | if def.mod_origin then 155 | return self.name..":"..def.mod_origin 156 | end 157 | end, 158 | get_keyword = function(self, group) 159 | if group.name ~= self.name then 160 | return group.parent.childs[group.name] 161 | end 162 | end 163 | }) 164 | 165 | filter.register_filter({ 166 | name = "translucent", 167 | check_item_by_def = function(self, def) 168 | if def.sunlight_propagates ~= 0 then 169 | return def.sunlight_propagates 170 | end 171 | end, 172 | }) 173 | 174 | filter.register_filter({ 175 | name = "light", 176 | check_item_by_def = function(self, def) 177 | if def.light_source ~= 0 then 178 | return def.light_source 179 | end 180 | end, 181 | }) 182 | 183 | filter.register_filter({ 184 | name = "metainv", 185 | check_item_by_def = function(self, def) 186 | if def.allow_metadata_inventory_move or 187 | def.allow_metadata_inventory_take or 188 | def.allow_metadata_inventory_put or 189 | def.on_metadata_inventory_move or 190 | def.on_metadata_inventory_take or 191 | def.on_metadata_inventory_put then 192 | return true 193 | end 194 | end, 195 | }) 196 | 197 | --[[ does it sense to filter them? I cannot define the human readable groups for them 198 | filter.register_filter({ 199 | name = "drawtype", 200 | check_item_by_def = function(self, def) 201 | if def.drawtype ~= "normal" then 202 | return def.drawtype 203 | end 204 | end, 205 | }) 206 | ]] 207 | 208 | local shaped_groups = {} 209 | local shaped_list = minetest.setting_get("smart_inventory_shaped_groups") or "carpet,door,fence,stair,slab,wall,micro,panel,slope" 210 | if shaped_list then 211 | for z in shaped_list:gmatch("[^,]+") do 212 | shaped_groups[z] = true 213 | end 214 | end 215 | 216 | filter.register_filter({ 217 | name = "shape", 218 | check_item_by_def = function(self, def) 219 | local door_groups 220 | if shaped_groups["door"] then 221 | local door_filter = filter.get("door") 222 | door_groups = door_filter:check_item_by_def(def) 223 | if door_groups and door_groups.door then 224 | return true 225 | end 226 | end 227 | 228 | for k, v in pairs(def.groups) do 229 | if k ~= "door" and shaped_groups[k] then 230 | return true 231 | end 232 | end 233 | end, 234 | }) 235 | 236 | --[[ disabled since debug.getupvalue is not usable to secure environment 237 | filter.register_filter({ 238 | name = "food", 239 | check_item_by_def = function(self, def) 240 | if def.on_use then 241 | local name,change=debug.getupvalue(def.on_use, 1) 242 | if name~=nil and name=="hp_change" and change > 0 then 243 | return tostring(change) 244 | end 245 | end 246 | end, 247 | }) 248 | 249 | filter.register_filter({ 250 | name = "toxic", 251 | check_item_by_def = function(self, def) 252 | if def.on_use then 253 | local name,change=debug.getupvalue(def.on_use, 1) 254 | if name~=nil and name=="hp_change" and change < 0 then 255 | return tostring(change) 256 | end 257 | end 258 | end, 259 | }) 260 | ]] 261 | 262 | filter.register_filter({ 263 | name = "tool", 264 | check_item_by_def = function(self, def) 265 | if not def.tool_capabilities then 266 | return 267 | end 268 | local rettab = {} 269 | for k, v in pairs(def.tool_capabilities) do 270 | if type(v) ~= "table" and v ~= 0 then 271 | rettab["tool:"..k] = v 272 | end 273 | end 274 | if def.tool_capabilities.damage_groups then 275 | for k, v in pairs(def.tool_capabilities.damage_groups) do 276 | if v ~= 0 then 277 | rettab["damage:"..k] = v 278 | end 279 | end 280 | end 281 | --[[ disabled, I cannot find right human readable interpretation for this 282 | if def.tool_capabilities.groupcaps then 283 | for groupcap, gdef in pairs(def.tool_capabilities.groupcaps) do 284 | for k, v in pairs(gdef) do 285 | if type(v) ~= "table" then 286 | rettab["groupcaps:"..groupcap..":"..k] = v 287 | end 288 | end 289 | end 290 | end 291 | ]] 292 | return rettab 293 | end, 294 | get_keyword = function(self, group) 295 | if group.name == "tool" or group.name == "damage" then 296 | return nil 297 | else 298 | return self:_get_keyword(group) 299 | end 300 | end 301 | }) 302 | 303 | filter.register_filter({ 304 | name = "armor", 305 | check_item_by_def = function(self, def) 306 | return def.armor_groups 307 | end, 308 | }) 309 | 310 | filter.register_filter({ 311 | name = 'clothing_cape', 312 | check_item_by_def = function(self, def) 313 | if def.groups.cape then 314 | return 'clothing' 315 | end 316 | end 317 | }) 318 | 319 | 320 | -- Burn times 321 | filter.register_filter({ 322 | name = "fuel", 323 | check_item_by_def = function(self, def) 324 | local burntime = minetest.get_craft_result({method="fuel",width=1,items={def.name}}).time 325 | if burntime > 0 then 326 | return "fuel:"..burntime 327 | end 328 | end 329 | }) 330 | 331 | 332 | -- Group assignment done in cache framework internally 333 | filter.register_filter({ 334 | name = "recipetype", 335 | check_item_by_def = function(self, def) end, 336 | get_keyword = function(self, group) 337 | if group.name ~= self.name then 338 | return group.parent.childs[group.name] 339 | end 340 | end 341 | }) 342 | 343 | -- Group assignment done in cache framework internally 344 | filter.register_filter({ 345 | name = "ingredient", 346 | check_item_by_def = function(self, def) end, 347 | get_description = function(self, group) 348 | local itemname = group.name:sub(12) 349 | if txt and txt["ingredient"] and 350 | minetest.registered_items[itemname] and minetest.registered_items[itemname].description then 351 | return txt["ingredient"] .." "..minetest.registered_items[itemname].description.." ("..group.name..")" 352 | else 353 | return group.name 354 | end 355 | end, 356 | get_keyword = function(self, group) 357 | -- not searchable by ingedient 358 | return nil 359 | end, 360 | is_valid = function(self, groupname) 361 | local itemname = groupname:sub(12) 362 | if itemname ~= "" and minetest.registered_items[itemname] then 363 | return true 364 | end 365 | end 366 | }) 367 | 368 | 369 | local door_groups 370 | local function fill_door_groups() 371 | door_groups = {} 372 | for _, extend_def in pairs(minetest.registered_items) do 373 | local base_def 374 | if extend_def.groups and extend_def.groups.door then 375 | if extend_def.door then 376 | base_def = minetest.registered_items[extend_def.door.name] 377 | elseif extend_def.drop and type(extend_def.drop) == "string" then 378 | base_def = minetest.registered_items[extend_def.drop] 379 | end 380 | end 381 | if base_def then 382 | door_groups[base_def.name] = extend_def 383 | door_groups[extend_def.name] = false 384 | end 385 | end 386 | end 387 | 388 | filter.register_filter({ 389 | name = "door", 390 | check_item_by_def = function(self, def) 391 | if not door_groups then 392 | fill_door_groups() 393 | end 394 | if not door_groups[def.name] then 395 | return 396 | end 397 | 398 | local group_filter = filter.get("group") 399 | local ret = group_filter:check_item_by_def(door_groups[def.name]) 400 | if ret then 401 | ret["not_in_creative_inventory"] = nil 402 | return ret 403 | end 404 | end 405 | }) 406 | 407 | ---------------- 408 | return filter 409 | 410 | -------------------------------------------------------------------------------- /libs/maininv.lua: -------------------------------------------------------------------------------- 1 | -- Enhanced main inventory methods 2 | local maininvClass = {} 3 | maininvClass_mt = {__index = maininvClass} 4 | 5 | -- Clear the inventory 6 | function maininvClass:remove_all() 7 | for idx = 1, self.inventory:get_size("main") do 8 | self.inventory:set_stack("main", idx, "") 9 | end 10 | end 11 | 12 | -- Save inventory content to a slot (file) 13 | function maininvClass:save_to_slot(slot) 14 | local savedata = {} 15 | for idx, stack in ipairs(self.inventory:get_list("main")) do 16 | if not stack:is_empty() then 17 | savedata[idx] = stack:to_string() 18 | end 19 | end 20 | 21 | local player = minetest.get_player_by_name(self.playername) 22 | player:set_attribute("inv_save_slot_"..tostring(slot), minetest.serialize(savedata)) 23 | end 24 | 25 | -- Get restore the inventory content from a slot (file) 26 | function maininvClass:restore_from_slot(slot) 27 | local player = minetest.get_player_by_name(self.playername) 28 | local savedata = minetest.deserialize(player:get_attribute("inv_save_slot_"..tostring(slot))) 29 | if savedata then 30 | for idx = 1, self.inventory:get_size("main") do 31 | self.inventory:set_stack("main", idx, savedata[idx]) 32 | end 33 | end 34 | end 35 | 36 | -- Add a item to inventory 37 | function maininvClass:add_item(item) 38 | return self.inventory:add_item("main", item) 39 | end 40 | 41 | function maininvClass:add_sepearate_stack(item) 42 | for idx, stack in ipairs(self.inventory:get_list("main")) do 43 | if stack:is_empty() then 44 | self.inventory:set_stack("main", idx, item) 45 | item = "" 46 | break 47 | end 48 | end 49 | return item 50 | end 51 | 52 | -- Get inventory content as consolidated table 53 | function maininvClass:get_items() 54 | local items_in_inventory = {} 55 | 56 | for _, stack in ipairs(self.inventory:get_list("main")) do 57 | local itemname = stack:get_name() 58 | if itemname and itemname ~= "" then 59 | if not items_in_inventory[itemname] then 60 | items_in_inventory[itemname] = stack:get_count() 61 | else 62 | items_in_inventory[itemname] = items_in_inventory[itemname] + stack:get_count() 63 | end 64 | end 65 | end 66 | 67 | -- add items in crafting field to the available items in inventory 68 | for _, stack in ipairs(self.inventory:get_list("craft")) do 69 | local itemname = stack:get_name() 70 | if itemname and itemname ~= "" then 71 | if not items_in_inventory[itemname] then 72 | items_in_inventory[itemname] = stack:get_count() 73 | else 74 | items_in_inventory[itemname] = items_in_inventory[itemname] + stack:get_count() 75 | end 76 | end 77 | end 78 | 79 | return items_in_inventory 80 | end 81 | 82 | -- try to get empty stacks by move items to other stacky up to max_size 83 | function maininvClass:compress() 84 | for idx1 = self.inventory:get_size("main"), 1, -1 do 85 | local stack1 = self.inventory:get_stack("main", idx1) 86 | if not stack1:is_empty() then 87 | for idx2 = 1, idx1 do 88 | local stack2 = self.inventory:get_stack("main", idx2) 89 | if idx1 ~= idx2 and stack1:get_name() == stack2:get_name() then 90 | stack1 = stack2:add_item(stack1) 91 | self.inventory:set_stack("main", idx1, stack1) 92 | self.inventory:set_stack("main", idx2, stack2) 93 | if stack1:is_empty() then 94 | break 95 | end 96 | end 97 | end 98 | end 99 | end 100 | end 101 | 102 | -- move items to crafting grid to craft item 103 | function maininvClass:craft_item(grid) 104 | for idx_main, stack_main in ipairs(self.inventory:get_list("main")) do 105 | for x, col in pairs(grid) do 106 | for y, item in pairs(col) do 107 | local idx_craft = (y-1)*3+x 108 | local stack_craft = self.inventory:get_stack("craft", idx_craft ) 109 | if not stack_main:is_empty() and stack_main:get_name() == item then --right item 110 | local left = stack_craft:add_item(stack_main:take_item(1)) 111 | stack_main:add_item(left) 112 | self.inventory:set_stack("craft", idx_craft, stack_craft) 113 | self.inventory:set_stack("main", idx_main, stack_main) 114 | end 115 | end 116 | end 117 | end 118 | end 119 | 120 | 121 | -- move all items from crafting inventory back to main inventory 122 | function maininvClass:sweep_crafting_inventory() 123 | for idx = 1, self.inventory:get_size("craft") do 124 | local stack = self.inventory:get_stack("craft", idx) 125 | if not stack:is_empty() then 126 | local left = self.inventory:add_item("main", stack) 127 | self.inventory:set_stack("craft", idx, left) 128 | end 129 | end 130 | end 131 | 132 | -- Swap row to the top. Asumption the inventory is 8x4, the row number should be 2, 3 or 4 133 | function maininvClass:swap_row_to_top(row) 134 | local width = 8 135 | for idx1 = 1, width do 136 | local idx2 = (row -1) * width + idx1 137 | local stack1 = self.inventory:get_stack("main", idx1) 138 | local stack2 = self.inventory:get_stack("main", idx2) 139 | self.inventory:set_stack("main", idx2, stack1) 140 | self.inventory:set_stack("main", idx1, stack2) 141 | end 142 | end 143 | 144 | -- player inventory class 145 | local maininv = {} 146 | function maininv.get(playername) 147 | local self = setmetatable({}, maininvClass_mt) 148 | self.playername = playername 149 | self.inventory = minetest.get_player_by_name(playername):get_inventory() 150 | self.inventory:set_width("craft", 3) 151 | self.inventory:set_size("craft", 9) 152 | return self 153 | end 154 | 155 | -- Check if player has creative privilege. 156 | function maininvClass:get_has_creative() 157 | return minetest.is_creative_enabled(self.playername) 158 | end 159 | 160 | return maininv 161 | -------------------------------------------------------------------------------- /libs/simple_po_reader.lua: -------------------------------------------------------------------------------- 1 | local txt_usage = minetest.setting_getbool("smart_inventory_friendly_group_names") --or true 2 | if txt_usage == false then 3 | return false 4 | end 5 | 6 | local modpath = minetest.get_modpath(minetest.get_current_modname()).."/locale" 7 | 8 | local LANG = minetest.setting_get("language") 9 | if not (LANG and (LANG ~= "")) then LANG = os.getenv("LANG") end 10 | if not (LANG and (LANG ~= "")) then LANG = "en" end 11 | local pofile = modpath.."/groups_"..LANG:sub(1,2)..".po" 12 | 13 | local f=io.open(pofile,"r") 14 | --fallback to en 15 | if not f then 16 | pofile = modpath.."/groups_en.po" 17 | f=io.open(pofile,"r") 18 | end 19 | 20 | local texttab = {} 21 | 22 | local msgid 23 | local msgstr 24 | 25 | for line in f:lines() do 26 | if line:sub(1,5) == 'msgid' then -- msgid "" 27 | msgid = line:sub(8, line:len()-1) 28 | elseif line:sub(1,6) == 'msgstr' then -- msgstr "" 29 | msgstr = line:sub(9, line:len()-1) 30 | end 31 | if msgid and msgstr then 32 | if msgid ~= "" and msgstr ~= "" then 33 | texttab[msgid] = msgstr 34 | end 35 | msgid = nil 36 | msgstr = nil 37 | end 38 | end 39 | 40 | io.close(f) 41 | return texttab 42 | -------------------------------------------------------------------------------- /libs/smartfs-elements.lua: -------------------------------------------------------------------------------- 1 | local smartfs = smart_inventory.smartfs 2 | local elements = {} 3 | 4 | ----------------------------------------------------- 5 | --- Crafting Preview applet 6 | ----------------------------------------------------- 7 | -- enhanced / prepared container 8 | -- Additional methods 9 | -- craft_preview:setCraft(craft) 10 | -- craft_preview:onButtonClicked(function(self, itemname, player)) 11 | -- if craft=nil, the view will be initialized 12 | 13 | local craft_preview = table.copy(smartfs._edef.container) 14 | function craft_preview:onCreate() 15 | self.data.relative = true 16 | smartfs._edef.container.onCreate(self) 17 | for x = 1, 3 do 18 | for y = 1, 3 do 19 | local button = self._state:image_button( 20 | (x-1)*self.data.zoom+self.data.pos.x, 21 | (y-1)*self.data.zoom+self.data.pos.y, 22 | self.data.zoom, self.data.zoom, 23 | "craft:"..x..":"..y,"") 24 | button:setVisible(false) 25 | button:onClick(function(self, state, player) 26 | local parent_element = state.location.containerElement 27 | if parent_element._button_click then 28 | parent_element._button_click(parent_element, self.data.item, player) 29 | end 30 | end) 31 | end 32 | end 33 | if self.data.recipe then 34 | self:setCraft(self.data.recipe) 35 | end 36 | end 37 | 38 | function craft_preview:onButtonClicked(func) 39 | self._button_click = func 40 | end 41 | 42 | -- Update fields 43 | function craft_preview:setCraft(craft) 44 | local width 45 | if craft then -- adjust width to 1 if the recipe contains just 1 item 46 | width = craft.width or 3 47 | if width == 0 then 48 | width = 3 49 | end 50 | if craft.items[1] and next(craft.items, 1) == nil then 51 | width = 1 52 | end 53 | end 54 | for x = 1, 3 do 55 | for y = 1, 3 do 56 | local item = nil 57 | if craft then 58 | if width <= 1 then 59 | if x == 2 then 60 | item = craft.items[y] 61 | end 62 | elseif x <= width then 63 | item = craft.items[(y-1)*width+x] 64 | end 65 | end 66 | local btn = self._state:get("craft:"..x..":"..y) 67 | if item then 68 | if type(item) == "string" then 69 | btn:setItem(item) 70 | btn:setTooltip() 71 | btn:setText("") 72 | else 73 | btn:setItem(item.item) 74 | btn:setTooltip(item.tooltip) 75 | btn:setText(item.text) 76 | end 77 | btn:setVisible(true) 78 | else 79 | btn:setVisible(false) 80 | end 81 | end 82 | end 83 | end 84 | 85 | -- get the preview as table 86 | function craft_preview:getCraft() 87 | local grid = {} 88 | for x = 1, 3 do 89 | grid[x] = {} 90 | for y = 1, 3 do 91 | local button = self._state:get("craft:"..x..":"..y) 92 | if button:getVisible() then 93 | grid[x][y] = button:getItem() 94 | end 95 | end 96 | end 97 | return grid 98 | end 99 | 100 | smartfs.element("craft_preview", craft_preview) 101 | 102 | function elements:craft_preview(x, y, name, zoom, recipe) 103 | return self:element("craft_preview", { 104 | pos = {x=x, y=y}, 105 | name = name, 106 | recipe = recipe, 107 | zoom = zoom or 1 108 | }) 109 | end 110 | 111 | 112 | ----------------------------------------------------- 113 | --- Pagable grid buttons 114 | ----------------------------------------------------- 115 | --[[ enhanced / prepared container 116 | Additional methods 117 | buttons_grid:setList(craft) 118 | buttons_grid:onClick(function(state, index, player)...end) 119 | buttons_grid:setList(iconlist) 120 | buttons_grid:getFirstVisible() 121 | buttons_grid:setFirstVisible(index) 122 | 123 | iconslist is a list of next entries: 124 | entry = { 125 | image | item = 126 | tooltip= 127 | is_button = true, 128 | size = {w=,h=} 129 | } 130 | ]] 131 | local buttons_grid = table.copy(smartfs._edef.container) 132 | function buttons_grid:onCreate() 133 | self.data.relative = true 134 | assert(self.data.size and self.data.size.w and self.data.size.h, "button needs valid size") 135 | smartfs._edef.container.onCreate(self) 136 | if not self.data.cell_size or not self.data.cell_size.w or not self.data.cell_size.h then 137 | self.data.cell_size = {w=1, h=1} 138 | end 139 | self:setSize(self.data.size.w, self.data.size.h) -- view size for background 140 | self.data.grid_size = {w = math.floor(self.data.size.w/self.data.cell_size.w), h = math.floor(self.data.size.h/self.data.cell_size.h)} 141 | self.data.list_start = self.data.list_start or 1 142 | self.data.list = self.data.list or {} 143 | for x = 1, self.data.grid_size.w do 144 | for y=1, self.data.grid_size.h do 145 | local button = self._state:button( 146 | self.data.pos.x + (x-1)*self.data.cell_size.w, 147 | self.data.pos.y + (y-1)*self.data.cell_size.h, 148 | self.data.cell_size.w, 149 | self.data.cell_size.h, 150 | tostring((y-1)*self.data.grid_size.w+x), 151 | "") 152 | button:onClick(function(self, state, player) 153 | local rel = tonumber(self.name) 154 | local parent_element = state.location.containerElement 155 | local idx = rel 156 | if parent_element.data.list_start > 1 then 157 | idx = parent_element.data.list_start + rel - 2 158 | end 159 | if rel == 1 and parent_element.data.list_start > 1 then 160 | -- page back 161 | local full_pagesize = parent_element.data.grid_size.w * parent_element.data.grid_size.h 162 | if parent_element.data.list_start <= full_pagesize then 163 | parent_element.data.list_start = 1 164 | else 165 | --prev page use allways 2x navigation buttons at list_start > 1 and the next page (we navigate from) exists 166 | parent_element.data.list_start = parent_element.data.list_start - (full_pagesize-2) 167 | end 168 | parent_element:update() 169 | elseif rel == (parent_element.data.grid_size.w * parent_element.data.grid_size.h) and 170 | parent_element.data.list[parent_element.data.list_start+parent_element.data.pagesize] then 171 | -- page forward 172 | parent_element.data.list_start = parent_element.data.list_start+parent_element.data.pagesize 173 | parent_element:update() 174 | else 175 | -- pass call to the button function 176 | if parent_element._click then 177 | parent_element:_click(parent_element.root, idx, player) 178 | end 179 | end 180 | end) 181 | button:setVisible(false) 182 | end 183 | end 184 | end 185 | 186 | function buttons_grid:reset(x, y, w, h, col_size, row_size) 187 | self._state = nil 188 | 189 | self.data.pos.x = x or self.data.pos.x 190 | self.data.pos.y = y or self.data.pos.y 191 | self.data.size.w = w or self.data.size.w 192 | self.data.size.h = h or self.data.size.h 193 | self.data.cell_size.w = col_size or self.data.cell_size.w 194 | self.data.cell_size.h = row_size or self.data.cell_size.h 195 | 196 | self:onCreate() 197 | self:update() 198 | end 199 | 200 | function buttons_grid:onClick(func) 201 | self._click = func 202 | end 203 | function buttons_grid:getFirstVisible() 204 | return self.data.list_start 205 | end 206 | function buttons_grid:setFirstVisible(idx) 207 | self.data.list_start = idx 208 | end 209 | function buttons_grid:setList(iconlist) 210 | self.data.list = iconlist or {} 211 | self:update() 212 | end 213 | 214 | function buttons_grid:update() 215 | --init pagesize 216 | self.data.pagesize = self.data.grid_size.w * self.data.grid_size.h 217 | --adjust start position 218 | if self.data.list_start > #self.data.list then 219 | self.data.list_start = #self.data.list - self.data.pagesize 220 | end 221 | if self.data.list_start < 1 then 222 | self.data.list_start = 1 223 | end 224 | 225 | local itemindex = self.data.list_start 226 | for btnid = 1, self.data.grid_size.w * self.data.grid_size.h do 227 | local button = self._state:get(tostring(btnid)) 228 | if btnid == 1 and self.data.list_start > 1 then 229 | -- setup back button 230 | button:setVisible(true) 231 | button:setImage("smart_inventory_left_arrow.png") 232 | button:setText(tostring(self.data.list_start-1)) 233 | button:setSize(self.data.cell_size.w, self.data.cell_size.h) 234 | self.data.pagesize = self.data.pagesize - 1 235 | elseif btnid == self.data.grid_size.w * self.data.grid_size.h and self.data.list[itemindex+1] then 236 | -- setup next button 237 | button:setVisible(true) 238 | button:setImage("smart_inventory_right_arrow.png") 239 | self.data.pagesize = self.data.pagesize - 1 240 | button:setText(tostring(#self.data.list-self.data.list_start-self.data.pagesize+1)) 241 | button:setSize(self.data.cell_size.w, self.data.cell_size.h) 242 | else 243 | -- functional button 244 | local entry = self.data.list[itemindex] 245 | if entry then 246 | if entry.size then 247 | button:setSize(entry.size.w, entry.size.h) 248 | else 249 | button:setSize(self.data.cell_size.w, self.data.cell_size.h) 250 | end 251 | if entry.item and entry.is_button == true then 252 | button:setVisible(true) 253 | button:setItem(entry.item) 254 | button:setText(entry.text or "") 255 | button:setTooltip(nil) 256 | elseif entry.image and entry.is_button == true then 257 | button:setVisible(true) 258 | button:setImage(entry.image) 259 | button:setText(entry.text or "") 260 | button:setTooltip(entry.tooltip) 261 | -- TODO 1: entry.image to display *.png 262 | -- TODO 2: entry.text to display label on button 263 | -- TODO 3,4,5: is_button == false to get just pic or label without button 264 | end 265 | else 266 | button:setVisible(false) 267 | end 268 | itemindex = itemindex + 1 269 | end 270 | end 271 | end 272 | 273 | 274 | smartfs.element("buttons_grid", buttons_grid) 275 | 276 | function elements:buttons_grid(x, y, w, h, name, col_size, row_size) 277 | return self:element("buttons_grid", { 278 | pos = {x=x, y=y}, 279 | size = {w=w, h=h}, 280 | cell_size = {w=col_size, h=row_size}, 281 | name = name 282 | }) 283 | end 284 | 285 | 286 | ------------------------- 287 | return elements 288 | -------------------------------------------------------------------------------- /locale/groups_de.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: de\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.8.5\n" 13 | 14 | msgid "all" 15 | msgstr "Alles" 16 | 17 | msgid "other" 18 | msgstr "Weitere" 19 | 20 | msgid "antiportal" 21 | msgstr "" 22 | 23 | msgid "armor" 24 | msgstr "Rüstung" 25 | 26 | msgid "armor:feet" 27 | msgstr "Stiefel" 28 | 29 | msgid "armor:fire" 30 | msgstr "Feuerschutz" 31 | 32 | msgid "armor:head" 33 | msgstr "Kopfschutz" 34 | 35 | msgid "armor:heal" 36 | msgstr "Heilen" 37 | 38 | msgid "armor:legs" 39 | msgstr "Beinschutz" 40 | 41 | msgid "armor:shield" 42 | msgstr "Schild" 43 | 44 | msgid "armor:torso" 45 | msgstr "Körperschutz" 46 | 47 | msgid "armor:use" 48 | msgstr "Abnutzung bei Schaden" 49 | 50 | msgid "attached_node" 51 | msgstr "Verbundbar" 52 | 53 | msgid "bag" 54 | msgstr "Tasche" 55 | 56 | msgid "basecolor" 57 | msgstr "" 58 | 59 | msgid "basecolor:black" 60 | msgstr "" 61 | 62 | msgid "basecolor:blue" 63 | msgstr "" 64 | 65 | msgid "basecolor:brown" 66 | msgstr "" 67 | 68 | msgid "basecolor:cyan" 69 | msgstr "" 70 | 71 | msgid "basecolor:green" 72 | msgstr "" 73 | 74 | msgid "basecolor:grey" 75 | msgstr "" 76 | 77 | msgid "basecolor:magenta" 78 | msgstr "" 79 | 80 | msgid "basecolor:orange" 81 | msgstr "" 82 | 83 | msgid "basecolor:red" 84 | msgstr "" 85 | 86 | msgid "basecolor:white" 87 | msgstr "" 88 | 89 | msgid "basecolor:yellow" 90 | msgstr "" 91 | 92 | msgid "bed" 93 | msgstr "Bett" 94 | 95 | msgid "bendy" 96 | msgstr "" 97 | 98 | msgid "book" 99 | msgstr "Buch" 100 | 101 | msgid "bouncy" 102 | msgstr "Federnd" 103 | 104 | msgid "cannon" 105 | msgstr "" 106 | 107 | msgid "cannonstand" 108 | msgstr "" 109 | 110 | msgid "choppy" 111 | msgstr "Abgehackt" 112 | 113 | msgid "coal" 114 | msgstr "Kohle" 115 | 116 | msgid "color" 117 | msgstr "Farbe" 118 | 119 | msgid "color:blue" 120 | msgstr "" 121 | 122 | msgid "color:orange" 123 | msgstr "" 124 | 125 | msgid "color:red" 126 | msgstr "" 127 | 128 | msgid "color:violet" 129 | msgstr "" 130 | 131 | msgid "color:white" 132 | msgstr "" 133 | 134 | msgid "color:yellow" 135 | msgstr "" 136 | 137 | msgid "connect_to_raillike" 138 | msgstr "Schienenartig" 139 | 140 | msgid "cools_lava" 141 | msgstr "" 142 | 143 | msgid "cracky" 144 | msgstr "Knackig" 145 | 146 | msgid "crossbrace_connectable" 147 | msgstr "" 148 | 149 | msgid "crumbly" 150 | msgstr "Brüchig" 151 | 152 | msgid "customnode" 153 | msgstr "Dekorativer Block" 154 | 155 | msgid "damage" 156 | msgstr "Schaden" 157 | 158 | msgid "damage:fleshy" 159 | msgstr "Fleischverletzung" 160 | 161 | msgid "desert" 162 | msgstr "" 163 | 164 | msgid "dig_immediate" 165 | msgstr "Schnell abbaubar" 166 | 167 | msgid "dig_immediate:3" 168 | msgstr "Sofort abbaubar" 169 | 170 | msgid "disable_jump" 171 | msgstr "Klebrig" 172 | 173 | msgid "door" 174 | msgstr "Tür" 175 | 176 | msgid "dry_grass" 177 | msgstr "" 178 | 179 | msgid "dye" 180 | msgstr "Farbstoff" 181 | 182 | msgid "eatable" 183 | msgstr "Essbar" 184 | 185 | msgid "excolor" 186 | msgstr "" 187 | 188 | msgid "excolor:black" 189 | msgstr "" 190 | 191 | msgid "excolor:blue" 192 | msgstr "" 193 | 194 | msgid "excolor:cyan" 195 | msgstr "" 196 | 197 | msgid "excolor:darkgrey" 198 | msgstr "" 199 | 200 | msgid "excolor:green" 201 | msgstr "" 202 | 203 | msgid "excolor:grey" 204 | msgstr "" 205 | 206 | msgid "excolor:orange" 207 | msgstr "" 208 | 209 | msgid "excolor:red" 210 | msgstr "" 211 | 212 | msgid "excolor:red:violet" 213 | msgstr "" 214 | 215 | msgid "excolor:violet" 216 | msgstr "" 217 | 218 | msgid "excolor:white" 219 | msgstr "" 220 | 221 | msgid "excolor:yellow" 222 | msgstr "" 223 | 224 | msgid "fall_damage_add_percent" 225 | msgstr "Aufprallschaden" 226 | 227 | msgid "falling_node" 228 | msgstr "Fallend" 229 | 230 | msgid "false" 231 | msgstr "" 232 | 233 | msgid "fence" 234 | msgstr "Zaun" 235 | 236 | msgid "flammable" 237 | msgstr "Brennbar" 238 | 239 | msgid "fleshy" 240 | msgstr "Fleischig" 241 | 242 | msgid "flora" 243 | msgstr "Flora" 244 | 245 | msgid "flower" 246 | msgstr "Blume" 247 | 248 | msgid "food" 249 | msgstr "Nahrung" 250 | 251 | msgid "food:apple" 252 | msgstr "" 253 | 254 | msgid "food:blueberry" 255 | msgstr "" 256 | 257 | msgid "food:bowl" 258 | msgstr "" 259 | 260 | msgid "food:butter" 261 | msgstr "" 262 | 263 | msgid "food:cactus" 264 | msgstr "" 265 | 266 | msgid "food:carrot" 267 | msgstr "" 268 | 269 | msgid "food:cheese" 270 | msgstr "" 271 | 272 | msgid "food:chicken" 273 | msgstr "" 274 | 275 | msgid "food:choco" 276 | msgstr "" 277 | 278 | msgid "food:choco:powder" 279 | msgstr "" 280 | 281 | msgid "food:cocoa" 282 | msgstr "" 283 | 284 | msgid "food:cup" 285 | msgstr "" 286 | 287 | msgid "food:dark" 288 | msgstr "" 289 | 290 | msgid "food:dark:chocolate" 291 | msgstr "" 292 | 293 | msgid "food:egg" 294 | msgstr "" 295 | 296 | msgid "food:flour" 297 | msgstr "" 298 | 299 | msgid "food:lemon" 300 | msgstr "" 301 | 302 | msgid "food:meat" 303 | msgstr "" 304 | 305 | msgid "food:meat:raw" 306 | msgstr "" 307 | 308 | msgid "food:milk" 309 | msgstr "" 310 | 311 | msgid "food:milk:chocolate" 312 | msgstr "" 313 | 314 | msgid "food:nut" 315 | msgstr "" 316 | 317 | msgid "food:orange" 318 | msgstr "" 319 | 320 | msgid "food:pasta" 321 | msgstr "" 322 | 323 | msgid "food:potato" 324 | msgstr "" 325 | 326 | msgid "food:rhubarb" 327 | msgstr "" 328 | 329 | msgid "food:strawberry" 330 | msgstr "" 331 | 332 | msgid "food:sugar" 333 | msgstr "" 334 | 335 | msgid "food:tomato" 336 | msgstr "" 337 | 338 | msgid "food:walnut" 339 | msgstr "" 340 | 341 | msgid "food:wheat" 342 | msgstr "" 343 | 344 | msgid "fuel" 345 | msgstr "Brennstoff" 346 | 347 | msgid "grass" 348 | msgstr "Gras" 349 | 350 | msgid "grassland" 351 | msgstr "" 352 | 353 | msgid "gunpowder" 354 | msgstr "" 355 | 356 | msgid "hot" 357 | msgstr "" 358 | 359 | msgid "igniter" 360 | msgstr "" 361 | 362 | msgid "ingredient" 363 | msgstr "Erzeugt aus" 364 | 365 | msgid "key" 366 | msgstr "Schlüssel" 367 | 368 | msgid "lava" 369 | msgstr "" 370 | 371 | msgid "leaves" 372 | msgstr "Laub" 373 | 374 | msgid "level" 375 | msgstr "Wertvoll" 376 | 377 | msgid "light" 378 | msgstr "Lichtquelle" 379 | 380 | msgid "liquid" 381 | msgstr "" 382 | 383 | msgid "marble" 384 | msgstr "" 385 | 386 | msgid "meat" 387 | msgstr "" 388 | 389 | msgid "melty" 390 | msgstr "" 391 | 392 | msgid "metainv" 393 | msgstr "Mit Inventar" 394 | 395 | msgid "mod" 396 | msgstr "Mod" 397 | 398 | msgid "not_cuttable" 399 | msgstr "" 400 | 401 | msgid "not_in_creative_inventory" 402 | msgstr "" 403 | 404 | msgid "oddly_breakable_by_hand" 405 | msgstr "Ohne Werkzeug abbaubar" 406 | 407 | msgid "pane" 408 | msgstr "" 409 | 410 | msgid "physics" 411 | msgstr "" 412 | 413 | msgid "physics:gravity" 414 | msgstr "Erdanziehung" 415 | 416 | msgid "physics:speed" 417 | msgstr "Geschwindigkeit" 418 | 419 | msgid "plant" 420 | msgstr "" 421 | 422 | msgid "poison" 423 | msgstr "" 424 | 425 | msgid "potting_soil" 426 | msgstr "" 427 | 428 | msgid "puts_out_fire" 429 | msgstr "" 430 | 431 | msgid "rail" 432 | msgstr "" 433 | 434 | msgid "recipetype" 435 | msgstr "" 436 | 437 | msgid "recipetype:cooking" 438 | msgstr "Gekocht" 439 | 440 | msgid "rock" 441 | msgstr "" 442 | 443 | msgid "sand" 444 | msgstr "Sand" 445 | 446 | msgid "sapling" 447 | msgstr "" 448 | 449 | msgid "seed" 450 | msgstr "Samen" 451 | 452 | msgid "shape" 453 | msgstr "Geformt" 454 | 455 | msgid "slab" 456 | msgstr "Platte" 457 | 458 | msgid "snappy" 459 | msgstr "Schnittig" 460 | 461 | msgid "snowy" 462 | msgstr "" 463 | 464 | msgid "soil" 465 | msgstr "Erde" 466 | 467 | msgid "soil:1" 468 | msgstr "Ackerboden" 469 | 470 | msgid "soil:2" 471 | msgstr "Trockener Ackerboden" 472 | 473 | msgid "soil:3" 474 | msgstr "Nasser Ackerboden" 475 | 476 | msgid "spreading_dirt_type" 477 | msgstr "" 478 | 479 | msgid "stair" 480 | msgstr "Treppe" 481 | 482 | msgid "stick" 483 | msgstr "Stock" 484 | 485 | msgid "stone" 486 | msgstr "Stein" 487 | 488 | msgid "surface_hot" 489 | msgstr "" 490 | 491 | msgid "tar_block" 492 | msgstr "" 493 | 494 | msgid "tool" 495 | msgstr "" 496 | 497 | msgid "tool:full_punch_interval" 498 | msgstr "Verwendungsinterval" 499 | 500 | msgid "tool:max_drop_level" 501 | msgstr "Max drop Level" 502 | 503 | msgid "torch" 504 | msgstr "" 505 | 506 | msgid "translucent" 507 | msgstr "Lichtdurchläßig" 508 | 509 | msgid "tree" 510 | msgstr "Baum" 511 | 512 | msgid "type" 513 | msgstr "" 514 | 515 | msgid "type:craft" 516 | msgstr "Gegenstand" 517 | 518 | msgid "type:node" 519 | msgstr "Block" 520 | 521 | msgid "type:tool" 522 | msgstr "Werkzeug" 523 | 524 | msgid "ud_param2_colorable" 525 | msgstr "Färbbar" 526 | 527 | msgid "unicolor" 528 | msgstr "" 529 | 530 | msgid "unicolor:black" 531 | msgstr "" 532 | 533 | msgid "unicolor:blue" 534 | msgstr "" 535 | 536 | msgid "unicolor:cyan" 537 | msgstr "" 538 | 539 | msgid "unicolor:dark" 540 | msgstr "" 541 | 542 | msgid "unicolor:dark:green" 543 | msgstr "" 544 | 545 | msgid "unicolor:dark:orange" 546 | msgstr "" 547 | 548 | msgid "unicolor:darkgrey" 549 | msgstr "" 550 | 551 | msgid "unicolor:green" 552 | msgstr "" 553 | 554 | msgid "unicolor:grey" 555 | msgstr "" 556 | 557 | msgid "unicolor:light" 558 | msgstr "" 559 | 560 | msgid "unicolor:light:red" 561 | msgstr "" 562 | 563 | msgid "unicolor:orange" 564 | msgstr "" 565 | 566 | msgid "unicolor:red" 567 | msgstr "" 568 | 569 | msgid "unicolor:red:violet" 570 | msgstr "" 571 | 572 | msgid "unicolor:violet" 573 | msgstr "" 574 | 575 | msgid "unicolor:white" 576 | msgstr "" 577 | 578 | msgid "unicolor:yellow" 579 | msgstr "" 580 | 581 | msgid "vessel" 582 | msgstr "Behälter" 583 | 584 | msgid "wall" 585 | msgstr "Mauer" 586 | 587 | msgid "water" 588 | msgstr "Wasser" 589 | 590 | msgid "water_bucket" 591 | msgstr "Eimer" 592 | 593 | msgid "wet" 594 | msgstr "Nass" 595 | 596 | msgid "wood" 597 | msgstr "Holz" 598 | 599 | msgid "wool" 600 | msgstr "Wolle" 601 | 602 | #~ msgid "physics:jump" 603 | #~ msgstr "Sprunghöhe" 604 | 605 | #~ msgid "armor:state" 606 | #~ msgstr "Rüstungsstatus" 607 | 608 | #~ msgid "damage:choppy" 609 | #~ msgstr "Abgehackt" 610 | 611 | #~ msgid "slope" 612 | #~ msgstr "Neigung" 613 | 614 | #~ msgid "leavedecay" 615 | #~ msgstr "Verwelkbar" 616 | 617 | #~ msgid "damage:snappy" 618 | #~ msgstr "Schnittverletzung" 619 | 620 | #~ msgid "micro" 621 | #~ msgstr "Mikro" 622 | 623 | #~ msgid "panel" 624 | #~ msgstr "Paneel" 625 | 626 | #~ msgid "armor:level" 627 | #~ msgstr "Level der Rüstung" 628 | 629 | #~ msgid "carpet" 630 | #~ msgstr "Teppich" 631 | 632 | #~ msgid "explody" 633 | #~ msgstr "Explosiv" 634 | 635 | #~ msgid "armor:water" 636 | #~ msgstr "Wasserschutz" 637 | 638 | #~ msgid "radiation" 639 | #~ msgstr "Schutz gegen Radioaktivität" 640 | 641 | #~ msgid "tool:damage:choppy" 642 | #~ msgstr "Hack-Schaden" 643 | 644 | #~ msgid "transluc" 645 | #~ msgstr "Lichtdurchlässig" 646 | 647 | #~ msgid "tool:damage:snappy" 648 | #~ msgstr "Schnitt-Schaden" 649 | 650 | #~ msgid "tool:damage:fleshy" 651 | #~ msgstr "Wund-Schaden" 652 | -------------------------------------------------------------------------------- /locale/groups_en.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: en\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=iso-8859-1\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.8.5\n" 13 | 14 | msgid "all" 15 | msgstr "All items" 16 | 17 | msgid "other" 18 | msgstr "Other items" 19 | 20 | msgid "antiportal" 21 | msgstr "" 22 | 23 | msgid "armor" 24 | msgstr "Armor" 25 | 26 | msgid "armor:feet" 27 | msgstr "Feet protection" 28 | 29 | msgid "armor:fire" 30 | msgstr "Fire protection" 31 | 32 | msgid "armor:head" 33 | msgstr "Head protection" 34 | 35 | msgid "armor:heal" 36 | msgstr "Heal" 37 | 38 | msgid "armor:legs" 39 | msgstr "Legs protection" 40 | 41 | msgid "armor:shield" 42 | msgstr "Shield" 43 | 44 | msgid "armor:torso" 45 | msgstr "Torso protection" 46 | 47 | msgid "armor:use" 48 | msgstr "Wear on damage" 49 | 50 | msgid "attached_node" 51 | msgstr "Attachable" 52 | 53 | msgid "bag" 54 | msgstr "" 55 | 56 | msgid "basecolor" 57 | msgstr "" 58 | 59 | msgid "basecolor:black" 60 | msgstr "" 61 | 62 | msgid "basecolor:blue" 63 | msgstr "" 64 | 65 | msgid "basecolor:brown" 66 | msgstr "" 67 | 68 | msgid "basecolor:cyan" 69 | msgstr "" 70 | 71 | msgid "basecolor:green" 72 | msgstr "" 73 | 74 | msgid "basecolor:grey" 75 | msgstr "" 76 | 77 | msgid "basecolor:magenta" 78 | msgstr "" 79 | 80 | msgid "basecolor:orange" 81 | msgstr "" 82 | 83 | msgid "basecolor:red" 84 | msgstr "" 85 | 86 | msgid "basecolor:white" 87 | msgstr "" 88 | 89 | msgid "basecolor:yellow" 90 | msgstr "" 91 | 92 | msgid "bed" 93 | msgstr "Bed" 94 | 95 | msgid "bendy" 96 | msgstr "" 97 | 98 | msgid "book" 99 | msgstr "Book" 100 | 101 | msgid "bouncy" 102 | msgstr "Bouncy" 103 | 104 | msgid "cannon" 105 | msgstr "" 106 | 107 | msgid "cannonstand" 108 | msgstr "" 109 | 110 | msgid "choppy" 111 | msgstr "Choppy" 112 | 113 | msgid "coal" 114 | msgstr "Coal" 115 | 116 | msgid "color" 117 | msgstr "Colour" 118 | 119 | msgid "color:blue" 120 | msgstr "" 121 | 122 | msgid "color:orange" 123 | msgstr "" 124 | 125 | msgid "color:red" 126 | msgstr "" 127 | 128 | msgid "color:violet" 129 | msgstr "" 130 | 131 | msgid "color:white" 132 | msgstr "" 133 | 134 | msgid "color:yellow" 135 | msgstr "" 136 | 137 | msgid "connect_to_raillike" 138 | msgstr "Rail-like" 139 | 140 | msgid "cools_lava" 141 | msgstr "" 142 | 143 | msgid "cracky" 144 | msgstr "Cracky" 145 | 146 | msgid "crossbrace_connectable" 147 | msgstr "" 148 | 149 | msgid "crumbly" 150 | msgstr "Crumbly" 151 | 152 | msgid "customnode" 153 | msgstr "Decorative node" 154 | 155 | msgid "damage" 156 | msgstr "Damage" 157 | 158 | msgid "damage:fleshy" 159 | msgstr "Fleshy damage" 160 | 161 | msgid "desert" 162 | msgstr "Desert" 163 | 164 | msgid "dig_immediate" 165 | msgstr "Fast diggable" 166 | 167 | msgid "dig_immediate:3" 168 | msgstr "Immediate diggable" 169 | 170 | msgid "disable_jump" 171 | msgstr "Sticky" 172 | 173 | msgid "door" 174 | msgstr "Door" 175 | 176 | msgid "dry_grass" 177 | msgstr "Dry grass" 178 | 179 | msgid "dye" 180 | msgstr "Dye" 181 | 182 | msgid "eatable" 183 | msgstr "Eatable" 184 | 185 | msgid "excolor" 186 | msgstr "" 187 | 188 | msgid "excolor:black" 189 | msgstr "" 190 | 191 | msgid "excolor:blue" 192 | msgstr "" 193 | 194 | msgid "excolor:cyan" 195 | msgstr "" 196 | 197 | msgid "excolor:darkgrey" 198 | msgstr "" 199 | 200 | msgid "excolor:green" 201 | msgstr "" 202 | 203 | msgid "excolor:grey" 204 | msgstr "" 205 | 206 | msgid "excolor:orange" 207 | msgstr "" 208 | 209 | msgid "excolor:red" 210 | msgstr "" 211 | 212 | msgid "excolor:red:violet" 213 | msgstr "" 214 | 215 | msgid "excolor:violet" 216 | msgstr "" 217 | 218 | msgid "excolor:white" 219 | msgstr "" 220 | 221 | msgid "excolor:yellow" 222 | msgstr "" 223 | 224 | msgid "fall_damage_add_percent" 225 | msgstr "Fall damage" 226 | 227 | msgid "falling_node" 228 | msgstr "Falling" 229 | 230 | msgid "false" 231 | msgstr "" 232 | 233 | msgid "fence" 234 | msgstr "Fence" 235 | 236 | msgid "flammable" 237 | msgstr "Flammable" 238 | 239 | msgid "fleshy" 240 | msgstr "Fleshy" 241 | 242 | msgid "flora" 243 | msgstr "Flora" 244 | 245 | msgid "flower" 246 | msgstr "Flower" 247 | 248 | msgid "food" 249 | msgstr "Food" 250 | 251 | msgid "food:apple" 252 | msgstr "" 253 | 254 | msgid "food:blueberry" 255 | msgstr "" 256 | 257 | msgid "food:bowl" 258 | msgstr "" 259 | 260 | msgid "food:butter" 261 | msgstr "" 262 | 263 | msgid "food:cactus" 264 | msgstr "" 265 | 266 | msgid "food:carrot" 267 | msgstr "" 268 | 269 | msgid "food:cheese" 270 | msgstr "" 271 | 272 | msgid "food:chicken" 273 | msgstr "" 274 | 275 | msgid "food:choco" 276 | msgstr "" 277 | 278 | msgid "food:choco:powder" 279 | msgstr "" 280 | 281 | msgid "food:cocoa" 282 | msgstr "" 283 | 284 | msgid "food:cup" 285 | msgstr "" 286 | 287 | msgid "food:dark" 288 | msgstr "" 289 | 290 | msgid "food:dark:chocolate" 291 | msgstr "" 292 | 293 | msgid "food:egg" 294 | msgstr "" 295 | 296 | msgid "food:flour" 297 | msgstr "" 298 | 299 | msgid "food:lemon" 300 | msgstr "" 301 | 302 | msgid "food:meat" 303 | msgstr "" 304 | 305 | msgid "food:meat:raw" 306 | msgstr "" 307 | 308 | msgid "food:milk" 309 | msgstr "" 310 | 311 | msgid "food:milk:chocolate" 312 | msgstr "" 313 | 314 | msgid "food:nut" 315 | msgstr "" 316 | 317 | msgid "food:orange" 318 | msgstr "" 319 | 320 | msgid "food:pasta" 321 | msgstr "" 322 | 323 | msgid "food:potato" 324 | msgstr "" 325 | 326 | msgid "food:rhubarb" 327 | msgstr "" 328 | 329 | msgid "food:strawberry" 330 | msgstr "" 331 | 332 | msgid "food:sugar" 333 | msgstr "" 334 | 335 | msgid "food:tomato" 336 | msgstr "" 337 | 338 | msgid "food:walnut" 339 | msgstr "" 340 | 341 | msgid "food:wheat" 342 | msgstr "" 343 | 344 | msgid "fuel" 345 | msgstr "Fuel" 346 | 347 | msgid "grass" 348 | msgstr "Grass" 349 | 350 | msgid "grassland" 351 | msgstr "" 352 | 353 | msgid "gunpowder" 354 | msgstr "" 355 | 356 | msgid "hot" 357 | msgstr "Hot" 358 | 359 | msgid "igniter" 360 | msgstr "" 361 | 362 | msgid "ingredient" 363 | msgstr "Crafted with" 364 | 365 | msgid "key" 366 | msgstr "Key" 367 | 368 | msgid "lava" 369 | msgstr "" 370 | 371 | msgid "leaves" 372 | msgstr "Leaves" 373 | 374 | msgid "level" 375 | msgstr "Valuable" 376 | 377 | msgid "light" 378 | msgstr "Light source" 379 | 380 | msgid "liquid" 381 | msgstr "" 382 | 383 | msgid "marble" 384 | msgstr "" 385 | 386 | msgid "meat" 387 | msgstr "" 388 | 389 | msgid "melty" 390 | msgstr "" 391 | 392 | msgid "metainv" 393 | msgstr "With inventory" 394 | 395 | msgid "mod" 396 | msgstr "Mod" 397 | 398 | msgid "not_cuttable" 399 | msgstr "" 400 | 401 | msgid "not_in_creative_inventory" 402 | msgstr "" 403 | 404 | msgid "oddly_breakable_by_hand" 405 | msgstr "Oddly breakable" 406 | 407 | msgid "pane" 408 | msgstr "" 409 | 410 | msgid "physics" 411 | msgstr "" 412 | 413 | msgid "physics:gravity" 414 | msgstr "Gravity" 415 | 416 | msgid "physics:speed" 417 | msgstr "Walking speed" 418 | 419 | msgid "plant" 420 | msgstr "" 421 | 422 | msgid "poison" 423 | msgstr "" 424 | 425 | msgid "potting_soil" 426 | msgstr "" 427 | 428 | msgid "puts_out_fire" 429 | msgstr "" 430 | 431 | msgid "rail" 432 | msgstr "" 433 | 434 | msgid "recipetype" 435 | msgstr "" 436 | 437 | msgid "recipetype:cooking" 438 | msgstr "Cooking result" 439 | 440 | msgid "rock" 441 | msgstr "" 442 | 443 | msgid "sand" 444 | msgstr "Sand" 445 | 446 | msgid "sapling" 447 | msgstr "Sapling" 448 | 449 | msgid "seed" 450 | msgstr "Seed" 451 | 452 | msgid "shape" 453 | msgstr "Shape" 454 | 455 | msgid "slab" 456 | msgstr "Slab" 457 | 458 | msgid "snappy" 459 | msgstr "Snappy" 460 | 461 | msgid "snowy" 462 | msgstr "Snowy" 463 | 464 | msgid "soil" 465 | msgstr "Soil" 466 | 467 | msgid "soil:1" 468 | msgstr "Basic soil" 469 | 470 | msgid "soil:2" 471 | msgstr "Dry farming soil" 472 | 473 | msgid "soil:3" 474 | msgstr "Wet farming" 475 | 476 | msgid "spreading_dirt_type" 477 | msgstr "" 478 | 479 | msgid "stair" 480 | msgstr "Stair" 481 | 482 | msgid "stick" 483 | msgstr "Stick" 484 | 485 | msgid "stone" 486 | msgstr "Stone" 487 | 488 | msgid "surface_hot" 489 | msgstr "" 490 | 491 | msgid "tar_block" 492 | msgstr "" 493 | 494 | msgid "tool" 495 | msgstr "" 496 | 497 | msgid "tool:full_punch_interval" 498 | msgstr "Punch interval" 499 | 500 | msgid "tool:max_drop_level" 501 | msgstr "Max drop level" 502 | 503 | msgid "torch" 504 | msgstr "Torch" 505 | 506 | msgid "translucent" 507 | msgstr "Translucent" 508 | 509 | msgid "tree" 510 | msgstr "Tree" 511 | 512 | msgid "type" 513 | msgstr "" 514 | 515 | msgid "type:craft" 516 | msgstr "Item" 517 | 518 | msgid "type:node" 519 | msgstr "Node" 520 | 521 | msgid "type:tool" 522 | msgstr "Tool" 523 | 524 | msgid "ud_param2_colorable" 525 | msgstr "Colorable by dye punch" 526 | 527 | msgid "unicolor" 528 | msgstr "" 529 | 530 | msgid "unicolor:black" 531 | msgstr "" 532 | 533 | msgid "unicolor:blue" 534 | msgstr "" 535 | 536 | msgid "unicolor:cyan" 537 | msgstr "" 538 | 539 | msgid "unicolor:dark" 540 | msgstr "" 541 | 542 | msgid "unicolor:dark:green" 543 | msgstr "" 544 | 545 | msgid "unicolor:dark:orange" 546 | msgstr "" 547 | 548 | msgid "unicolor:darkgrey" 549 | msgstr "" 550 | 551 | msgid "unicolor:green" 552 | msgstr "" 553 | 554 | msgid "unicolor:grey" 555 | msgstr "" 556 | 557 | msgid "unicolor:light" 558 | msgstr "" 559 | 560 | msgid "unicolor:light:red" 561 | msgstr "" 562 | 563 | msgid "unicolor:orange" 564 | msgstr "" 565 | 566 | msgid "unicolor:red" 567 | msgstr "" 568 | 569 | msgid "unicolor:red:violet" 570 | msgstr "" 571 | 572 | msgid "unicolor:violet" 573 | msgstr "" 574 | 575 | msgid "unicolor:white" 576 | msgstr "" 577 | 578 | msgid "unicolor:yellow" 579 | msgstr "" 580 | 581 | msgid "vessel" 582 | msgstr "Vessel" 583 | 584 | msgid "wall" 585 | msgstr "Wall" 586 | 587 | msgid "water" 588 | msgstr "Water" 589 | 590 | msgid "water_bucket" 591 | msgstr "Bucket" 592 | 593 | msgid "wet" 594 | msgstr "" 595 | 596 | msgid "wood" 597 | msgstr "Wood" 598 | 599 | msgid "wool" 600 | msgstr "Wool" 601 | 602 | #~ msgid "physics:jump" 603 | #~ msgstr "Jump high" 604 | 605 | #~ msgid "armor:state" 606 | #~ msgstr "Armor state" 607 | 608 | #~ msgid "damage:choppy" 609 | #~ msgstr "Choppy damage" 610 | 611 | #~ msgid "slope" 612 | #~ msgstr "Slope" 613 | 614 | #~ msgid "leavedecay" 615 | #~ msgstr "Decayable" 616 | 617 | #~ msgid "damage:snappy" 618 | #~ msgstr "Snappy damage" 619 | 620 | #~ msgid "micro" 621 | #~ msgstr "Micro" 622 | 623 | #~ msgid "panel" 624 | #~ msgstr "Panel" 625 | 626 | #~ msgid "armor:level" 627 | #~ msgstr "Armor level" 628 | 629 | #~ msgid "carpet" 630 | #~ msgstr "Carpet" 631 | 632 | #~ msgid "explody" 633 | #~ msgstr "Explosive" 634 | 635 | #~ msgid "armor:water" 636 | #~ msgstr "Water protection" 637 | -------------------------------------------------------------------------------- /locale/groups_template.pot: -------------------------------------------------------------------------------- 1 | msgid "all" 2 | msgstr "" 3 | 4 | msgid "other" 5 | msgstr "" 6 | 7 | msgid "antiportal" 8 | msgstr "" 9 | 10 | msgid "armor" 11 | msgstr "" 12 | 13 | msgid "armor:feet" 14 | msgstr "" 15 | 16 | msgid "armor:fire" 17 | msgstr "" 18 | 19 | msgid "armor:head" 20 | msgstr "" 21 | 22 | msgid "armor:heal" 23 | msgstr "" 24 | 25 | msgid "armor:legs" 26 | msgstr "" 27 | 28 | msgid "armor:shield" 29 | msgstr "" 30 | 31 | msgid "armor:torso" 32 | msgstr "" 33 | 34 | msgid "armor:use" 35 | msgstr "" 36 | 37 | msgid "attached_node" 38 | msgstr "" 39 | 40 | msgid "bag" 41 | msgstr "" 42 | 43 | msgid "basecolor" 44 | msgstr "" 45 | 46 | msgid "basecolor:black" 47 | msgstr "" 48 | 49 | msgid "basecolor:blue" 50 | msgstr "" 51 | 52 | msgid "basecolor:brown" 53 | msgstr "" 54 | 55 | msgid "basecolor:cyan" 56 | msgstr "" 57 | 58 | msgid "basecolor:green" 59 | msgstr "" 60 | 61 | msgid "basecolor:grey" 62 | msgstr "" 63 | 64 | msgid "basecolor:magenta" 65 | msgstr "" 66 | 67 | msgid "basecolor:orange" 68 | msgstr "" 69 | 70 | msgid "basecolor:red" 71 | msgstr "" 72 | 73 | msgid "basecolor:white" 74 | msgstr "" 75 | 76 | msgid "basecolor:yellow" 77 | msgstr "" 78 | 79 | msgid "bed" 80 | msgstr "" 81 | 82 | msgid "bendy" 83 | msgstr "" 84 | 85 | msgid "book" 86 | msgstr "" 87 | 88 | msgid "bouncy" 89 | msgstr "" 90 | 91 | msgid "cannon" 92 | msgstr "" 93 | 94 | msgid "cannonstand" 95 | msgstr "" 96 | 97 | msgid "choppy" 98 | msgstr "" 99 | 100 | msgid "coal" 101 | msgstr "" 102 | 103 | msgid "color" 104 | msgstr "" 105 | 106 | msgid "color:blue" 107 | msgstr "" 108 | 109 | msgid "color:orange" 110 | msgstr "" 111 | 112 | msgid "color:red" 113 | msgstr "" 114 | 115 | msgid "color:violet" 116 | msgstr "" 117 | 118 | msgid "color:white" 119 | msgstr "" 120 | 121 | msgid "color:yellow" 122 | msgstr "" 123 | 124 | msgid "connect_to_raillike" 125 | msgstr "" 126 | 127 | msgid "cools_lava" 128 | msgstr "" 129 | 130 | msgid "cracky" 131 | msgstr "" 132 | 133 | msgid "crossbrace_connectable" 134 | msgstr "" 135 | 136 | msgid "crumbly" 137 | msgstr "" 138 | 139 | msgid "customnode" 140 | msgstr "" 141 | 142 | msgid "damage" 143 | msgstr "" 144 | 145 | msgid "damage:fleshy" 146 | msgstr "" 147 | 148 | msgid "desert" 149 | msgstr "" 150 | 151 | msgid "dig_immediate" 152 | msgstr "" 153 | 154 | msgid "dig_immediate:3" 155 | msgstr "" 156 | 157 | msgid "disable_jump" 158 | msgstr "" 159 | 160 | msgid "door" 161 | msgstr "" 162 | 163 | msgid "dry_grass" 164 | msgstr "" 165 | 166 | msgid "dye" 167 | msgstr "" 168 | 169 | msgid "eatable" 170 | msgstr "" 171 | 172 | msgid "excolor" 173 | msgstr "" 174 | 175 | msgid "excolor:black" 176 | msgstr "" 177 | 178 | msgid "excolor:blue" 179 | msgstr "" 180 | 181 | msgid "excolor:cyan" 182 | msgstr "" 183 | 184 | msgid "excolor:darkgrey" 185 | msgstr "" 186 | 187 | msgid "excolor:green" 188 | msgstr "" 189 | 190 | msgid "excolor:grey" 191 | msgstr "" 192 | 193 | msgid "excolor:orange" 194 | msgstr "" 195 | 196 | msgid "excolor:red" 197 | msgstr "" 198 | 199 | msgid "excolor:red:violet" 200 | msgstr "" 201 | 202 | msgid "excolor:violet" 203 | msgstr "" 204 | 205 | msgid "excolor:white" 206 | msgstr "" 207 | 208 | msgid "excolor:yellow" 209 | msgstr "" 210 | 211 | msgid "fall_damage_add_percent" 212 | msgstr "" 213 | 214 | msgid "falling_node" 215 | msgstr "" 216 | 217 | msgid "false" 218 | msgstr "" 219 | 220 | msgid "fence" 221 | msgstr "" 222 | 223 | msgid "flammable" 224 | msgstr "" 225 | 226 | msgid "fleshy" 227 | msgstr "" 228 | 229 | msgid "flora" 230 | msgstr "" 231 | 232 | msgid "flower" 233 | msgstr "" 234 | 235 | msgid "food" 236 | msgstr "" 237 | 238 | msgid "food:apple" 239 | msgstr "" 240 | 241 | msgid "food:blueberry" 242 | msgstr "" 243 | 244 | msgid "food:bowl" 245 | msgstr "" 246 | 247 | msgid "food:butter" 248 | msgstr "" 249 | 250 | msgid "food:cactus" 251 | msgstr "" 252 | 253 | msgid "food:carrot" 254 | msgstr "" 255 | 256 | msgid "food:cheese" 257 | msgstr "" 258 | 259 | msgid "food:chicken" 260 | msgstr "" 261 | 262 | msgid "food:choco" 263 | msgstr "" 264 | 265 | msgid "food:choco:powder" 266 | msgstr "" 267 | 268 | msgid "food:cocoa" 269 | msgstr "" 270 | 271 | msgid "food:cup" 272 | msgstr "" 273 | 274 | msgid "food:dark" 275 | msgstr "" 276 | 277 | msgid "food:dark:chocolate" 278 | msgstr "" 279 | 280 | msgid "food:egg" 281 | msgstr "" 282 | 283 | msgid "food:flour" 284 | msgstr "" 285 | 286 | msgid "food:lemon" 287 | msgstr "" 288 | 289 | msgid "food:meat" 290 | msgstr "" 291 | 292 | msgid "food:meat:raw" 293 | msgstr "" 294 | 295 | msgid "food:milk" 296 | msgstr "" 297 | 298 | msgid "food:milk:chocolate" 299 | msgstr "" 300 | 301 | msgid "food:nut" 302 | msgstr "" 303 | 304 | msgid "food:orange" 305 | msgstr "" 306 | 307 | msgid "food:pasta" 308 | msgstr "" 309 | 310 | msgid "food:potato" 311 | msgstr "" 312 | 313 | msgid "food:rhubarb" 314 | msgstr "" 315 | 316 | msgid "food:strawberry" 317 | msgstr "" 318 | 319 | msgid "food:sugar" 320 | msgstr "" 321 | 322 | msgid "food:tomato" 323 | msgstr "" 324 | 325 | msgid "food:walnut" 326 | msgstr "" 327 | 328 | msgid "food:wheat" 329 | msgstr "" 330 | 331 | msgid "fuel" 332 | msgstr "" 333 | 334 | msgid "grass" 335 | msgstr "" 336 | 337 | msgid "grassland" 338 | msgstr "" 339 | 340 | msgid "gunpowder" 341 | msgstr "" 342 | 343 | msgid "hot" 344 | msgstr "" 345 | 346 | msgid "igniter" 347 | msgstr "" 348 | 349 | msgid "ingredient" 350 | msgstr "" 351 | 352 | msgid "key" 353 | msgstr "" 354 | 355 | msgid "lava" 356 | msgstr "" 357 | 358 | msgid "leaves" 359 | msgstr "" 360 | 361 | msgid "level" 362 | msgstr "" 363 | 364 | msgid "light" 365 | msgstr "" 366 | 367 | msgid "liquid" 368 | msgstr "" 369 | 370 | msgid "marble" 371 | msgstr "" 372 | 373 | msgid "meat" 374 | msgstr "" 375 | 376 | msgid "melty" 377 | msgstr "" 378 | 379 | msgid "metainv" 380 | msgstr "" 381 | 382 | msgid "mod" 383 | msgstr "" 384 | 385 | msgid "not_cuttable" 386 | msgstr "" 387 | 388 | msgid "not_in_creative_inventory" 389 | msgstr "" 390 | 391 | msgid "oddly_breakable_by_hand" 392 | msgstr "" 393 | 394 | msgid "pane" 395 | msgstr "" 396 | 397 | msgid "physics" 398 | msgstr "" 399 | 400 | msgid "physics:gravity" 401 | msgstr "" 402 | 403 | msgid "physics:speed" 404 | msgstr "" 405 | 406 | msgid "plant" 407 | msgstr "" 408 | 409 | msgid "poison" 410 | msgstr "" 411 | 412 | msgid "potting_soil" 413 | msgstr "" 414 | 415 | msgid "puts_out_fire" 416 | msgstr "" 417 | 418 | msgid "rail" 419 | msgstr "" 420 | 421 | msgid "recipetype" 422 | msgstr "" 423 | 424 | msgid "recipetype:cooking" 425 | msgstr "" 426 | 427 | msgid "rock" 428 | msgstr "" 429 | 430 | msgid "sand" 431 | msgstr "" 432 | 433 | msgid "sapling" 434 | msgstr "" 435 | 436 | msgid "seed" 437 | msgstr "" 438 | 439 | msgid "shape" 440 | msgstr "" 441 | 442 | msgid "slab" 443 | msgstr "" 444 | 445 | msgid "snappy" 446 | msgstr "" 447 | 448 | msgid "snowy" 449 | msgstr "" 450 | 451 | msgid "soil" 452 | msgstr "" 453 | 454 | msgid "soil:1" 455 | msgstr "" 456 | 457 | msgid "soil:2" 458 | msgstr "" 459 | 460 | msgid "soil:3" 461 | msgstr "" 462 | 463 | msgid "spreading_dirt_type" 464 | msgstr "" 465 | 466 | msgid "stair" 467 | msgstr "" 468 | 469 | msgid "stick" 470 | msgstr "" 471 | 472 | msgid "stone" 473 | msgstr "" 474 | 475 | msgid "surface_hot" 476 | msgstr "" 477 | 478 | msgid "tar_block" 479 | msgstr "" 480 | 481 | msgid "tool" 482 | msgstr "" 483 | 484 | msgid "tool:full_punch_interval" 485 | msgstr "" 486 | 487 | msgid "tool:max_drop_level" 488 | msgstr "" 489 | 490 | msgid "torch" 491 | msgstr "" 492 | 493 | msgid "translucent" 494 | msgstr "" 495 | 496 | msgid "tree" 497 | msgstr "" 498 | 499 | msgid "type" 500 | msgstr "" 501 | 502 | msgid "type:craft" 503 | msgstr "" 504 | 505 | msgid "type:node" 506 | msgstr "" 507 | 508 | msgid "type:tool" 509 | msgstr "" 510 | 511 | msgid "ud_param2_colorable" 512 | msgstr "" 513 | 514 | msgid "unicolor" 515 | msgstr "" 516 | 517 | msgid "unicolor:black" 518 | msgstr "" 519 | 520 | msgid "unicolor:blue" 521 | msgstr "" 522 | 523 | msgid "unicolor:cyan" 524 | msgstr "" 525 | 526 | msgid "unicolor:dark" 527 | msgstr "" 528 | 529 | msgid "unicolor:dark:green" 530 | msgstr "" 531 | 532 | msgid "unicolor:dark:orange" 533 | msgstr "" 534 | 535 | msgid "unicolor:darkgrey" 536 | msgstr "" 537 | 538 | msgid "unicolor:green" 539 | msgstr "" 540 | 541 | msgid "unicolor:grey" 542 | msgstr "" 543 | 544 | msgid "unicolor:light" 545 | msgstr "" 546 | 547 | msgid "unicolor:light:red" 548 | msgstr "" 549 | 550 | msgid "unicolor:orange" 551 | msgstr "" 552 | 553 | msgid "unicolor:red" 554 | msgstr "" 555 | 556 | msgid "unicolor:red:violet" 557 | msgstr "" 558 | 559 | msgid "unicolor:violet" 560 | msgstr "" 561 | 562 | msgid "unicolor:white" 563 | msgstr "" 564 | 565 | msgid "unicolor:yellow" 566 | msgstr "" 567 | 568 | msgid "vessel" 569 | msgstr "" 570 | 571 | msgid "wall" 572 | msgstr "" 573 | 574 | msgid "water" 575 | msgstr "" 576 | 577 | msgid "water_bucket" 578 | msgstr "" 579 | 580 | msgid "wet" 581 | msgstr "" 582 | 583 | msgid "wood" 584 | msgstr "" 585 | 586 | msgid "wool" 587 | msgstr "" 588 | 589 | -------------------------------------------------------------------------------- /mod.conf: -------------------------------------------------------------------------------- 1 | name = smart_inventory 2 | title = Smart Inventory 3 | author = bell07 4 | license = CC0 5 | -------------------------------------------------------------------------------- /pages/awards.lua: -------------------------------------------------------------------------------- 1 | if not minetest.get_modpath("awards") then 2 | return 3 | end 4 | 5 | local function awards_callback(state) 6 | local codebox = state:element("code", { name = "code", code = "", playername = state.location.rootState.location.player }) 7 | codebox:onBuild(function(self) 8 | local formspec = awards.getFormspec(self.data.playername, self.data.playername, self.data.awards_idx or 1) 9 | 10 | -- patch elememt sizes and positions 11 | formspec = formspec:gsub('textarea%[0.25,3.75;3.9,4.2', 'textarea[-0.75,3.75;5.9,4.2') 12 | formspec = formspec:gsub('box%[%-0.05,3.75;3.9,4.2', 'box[-1.05,3.75;5.9,4.2') 13 | formspec = formspec:gsub('textlist%[4,0;3.8,8.6', 'textlist[6,0;6.8,8.6') 14 | self.data.code = "container[3,0]".. formspec .."container_end[]" 15 | end) 16 | 17 | state:onInput(function(state, fields, player) 18 | if fields.awards then 19 | local event = minetest.explode_textlist_event(fields.awards) 20 | if event.type == "CHG" then 21 | state:get("code").data.awards_idx = event.index 22 | end 23 | end 24 | end) 25 | end 26 | 27 | smart_inventory.register_page({ 28 | name = "awards", 29 | icon = "awards_ui_icon.png", 30 | tooltip = "Awards", 31 | smartfs_callback = awards_callback, 32 | sequence = 25, 33 | }) 34 | -------------------------------------------------------------------------------- /pages/crafting.lua: -------------------------------------------------------------------------------- 1 | local cache = smart_inventory.cache 2 | local crecipes = smart_inventory.crecipes 3 | local doc_addon = smart_inventory.doc_addon 4 | local ui_tools = smart_inventory.ui_tools 5 | 6 | ----------------------------------------------------- 7 | -- Update recipe preview item informations about the selected item 8 | ----------------------------------------------------- 9 | local function update_crafting_preview(state) 10 | local player = state.location.rootState.location.player 11 | local listentry = state.param.crafting_recipes_preview_listentry 12 | local selected = state.param.crafting_recipes_preview_selected 13 | local itemdef = listentry.itemdef 14 | local inf_state = state:get("inf_area"):getContainerState() 15 | local cr_type_img = state:get("cr_type_img") 16 | local craft_result = inf_state:get("craft_result") 17 | local group_list = inf_state:get("item_groups") 18 | 19 | -- get recipe to display, check paging buttons needed 20 | local all_recipes 21 | local valid_recipes = {} 22 | local recipe 23 | local revealed_items_cache = {} 24 | 25 | if listentry.recipes then -- preselected recipes (ie. craftable) 26 | all_recipes = listentry.recipes 27 | elseif cache.citems[listentry.item] then -- check all available recipes (ie. search) 28 | all_recipes = cache.citems[listentry.item].in_output_recipe or {} 29 | else -- no recipes 30 | all_recipes = {} 31 | end 32 | 33 | for _, recipe in ipairs(all_recipes) do 34 | if crecipes.crecipes[recipe]:is_revealed(player, revealed_items_cache) then 35 | table.insert(valid_recipes, recipe) 36 | end 37 | end 38 | 39 | if valid_recipes[1] then 40 | if not valid_recipes[selected] then 41 | selected = 1 42 | end 43 | state.param.crafting_recipes_preview_selected = selected 44 | if selected > 1 and valid_recipes[selected-1] then 45 | state:get("preview_prev"):setVisible(true) 46 | else 47 | state:get("preview_prev"):setVisible(false) 48 | end 49 | if valid_recipes[selected+1] then 50 | state:get("preview_next"):setVisible(true) 51 | else 52 | state:get("preview_next"):setVisible(false) 53 | end 54 | 55 | if valid_recipes[selected] then 56 | recipe = valid_recipes[selected] 57 | local crecipe = crecipes.crecipes[recipe] 58 | if crecipe then 59 | recipe = crecipe:get_with_placeholder(player, state.param.crafting_items_in_inventory) 60 | end 61 | end 62 | else 63 | state:get("preview_prev"):setVisible(false) 64 | state:get("preview_next"):setVisible(false) 65 | end 66 | 67 | -- display the recipe result or selected item 68 | if recipe then 69 | if recipe.type == "normal" then 70 | state:get("cr_type"):setText("") 71 | cr_type_img:setVisible(false) 72 | state:get("ac1"):setVisible(true) 73 | elseif recipe.type == "cooking" then 74 | state:get("cr_type"):setText(recipe.type) 75 | state:get("cr_type"):setText("") 76 | cr_type_img:setVisible(true) 77 | cr_type_img:setImage("smart_inventory_furnace.png") 78 | state:get("ac1"):setVisible(false) 79 | else 80 | state:get("cr_type"):setText(recipe.type) 81 | cr_type_img:setVisible(false) 82 | state:get("ac1"):setVisible(false) 83 | end 84 | craft_result:setImage(recipe.output) 85 | craft_result:setVisible() 86 | state:get("craft_preview"):setCraft(recipe) 87 | else 88 | state:get("cr_type"):setText("") 89 | state:get("craft_preview"):setCraft(nil) 90 | cr_type_img:setVisible(false) 91 | state:get("ac1"):setVisible(false) 92 | if itemdef then 93 | craft_result:setVisible(true) 94 | craft_result:setImage(itemdef.name) 95 | else 96 | craft_result:setVisible(false) 97 | end 98 | end 99 | 100 | -- display docs icon if revealed item 101 | if smart_inventory.doc_items_mod then 102 | inf_state:get("doc_btn"):setVisible(false) 103 | local outitem = craft_result:getImage() 104 | if outitem then 105 | for z in outitem:gmatch("[^%s]+") do 106 | if doc_addon.is_revealed_item(z, player) then 107 | inf_state:get("doc_btn"):setVisible(true) 108 | end 109 | break 110 | end 111 | end 112 | end 113 | 114 | -- update info area 115 | if itemdef then 116 | inf_state:get("info1"):setText(itemdef.description) 117 | inf_state:get("info2"):setText("("..itemdef.name..")") 118 | if itemdef._doc_items_longdesc then 119 | inf_state:get("info3"):setText(itemdef._doc_items_longdesc) 120 | else 121 | inf_state:get("info3"):setText("") 122 | end 123 | 124 | group_list:clearItems() 125 | cache.add_item(listentry.itemdef) -- Note: this addition does not affect the already prepared root lists 126 | if cache.citems[itemdef.name] then 127 | for _, groupdef in ipairs(ui_tools.get_tight_groups(cache.citems[itemdef.name].cgroups)) do 128 | group_list:addItem(groupdef.group_desc) 129 | end 130 | end 131 | elseif listentry.item then 132 | inf_state:get("info1"):setText("") 133 | inf_state:get("info2"):setText("("..listentry.item..")") 134 | inf_state:get("info3"):setText("") 135 | else 136 | inf_state:get("info1"):setText("") 137 | inf_state:get("info2"):setText("") 138 | inf_state:get("info3"):setText("") 139 | group_list:clearItems() 140 | end 141 | end 142 | 143 | ----------------------------------------------------- 144 | -- Update the group selection table 145 | ----------------------------------------------------- 146 | local function update_group_selection(state, rebuild) 147 | local grouped = state.param.crafting_grouped_items 148 | local groups_sel = state:get("groups_sel") 149 | local grid = state:get("buttons_grid") 150 | local label = state:get("inf_area"):getContainerState():get("groups_label") 151 | 152 | if rebuild then 153 | state.param.crafting_group_list = ui_tools.update_group_selection(grouped, groups_sel, state.param.crafting_group_list) 154 | end 155 | 156 | local sel_id = groups_sel:getSelected() 157 | if state.param.crafting_group_list[sel_id] then 158 | state.param.crafting_craftable_list = grouped[state.param.crafting_group_list[sel_id]].items 159 | table.sort(state.param.crafting_craftable_list, function(a,b) 160 | return a.sort_value < b.sort_value 161 | end) 162 | grid:setList(state.param.crafting_craftable_list) 163 | label:setText(groups_sel:getSelectedItem()) 164 | else 165 | label:setText("Empty List") 166 | grid:setList({}) 167 | end 168 | end 169 | 170 | ----------------------------------------------------- 171 | -- Update the items list 172 | ----------------------------------------------------- 173 | local function update_from_recipelist(state, recipelist, preview_item, replace_not_in_list) 174 | local old_preview_entry, old_preview_item, new_preview_entry, new_preview_item 175 | if state.param.crafting_recipes_preview_listentry then 176 | old_preview_item = state.param.crafting_recipes_preview_listentry.item 177 | end 178 | if preview_item == "" then 179 | new_preview_item = nil 180 | else 181 | new_preview_item = preview_item 182 | end 183 | 184 | local duplicate_index_tmp = {} 185 | local craftable_itemlist = {} 186 | 187 | for recipe, _ in pairs(recipelist) do 188 | local def = crecipes.crecipes[recipe].out_item 189 | local itemname = def.name 190 | if duplicate_index_tmp[itemname] then 191 | table.insert(duplicate_index_tmp[itemname].recipes, recipe) 192 | else 193 | local entry = {} 194 | for k,v in pairs(cache.citems[itemname].ui_item) do 195 | entry[k] = v 196 | end 197 | 198 | entry.recipes = {} 199 | duplicate_index_tmp[itemname] = entry 200 | table.insert(entry.recipes, recipe) 201 | table.insert(craftable_itemlist, entry) 202 | if new_preview_item and itemname == new_preview_item then 203 | new_preview_entry = entry 204 | end 205 | if old_preview_item and itemname == old_preview_item then 206 | old_preview_entry = entry 207 | end 208 | end 209 | end 210 | 211 | -- update crafting preview if the old is not in list anymore 212 | if new_preview_item then 213 | if not replace_not_in_list or not old_preview_entry then 214 | if not new_preview_entry then 215 | new_preview_entry = { 216 | itemdef = minetest.registered_items[new_preview_item], 217 | item = new_preview_item 218 | } 219 | end 220 | state.param.crafting_recipes_preview_selected = 1 221 | state.param.crafting_recipes_preview_listentry = new_preview_entry 222 | update_crafting_preview(state) 223 | if state:get("info_tog"):getId() == 1 then 224 | state:get("info_tog"):submit() 225 | end 226 | end 227 | elseif replace_not_in_list and not old_preview_entry then 228 | state.param.crafting_recipes_preview_selected = 1 229 | state.param.crafting_recipes_preview_listentry = {} 230 | update_crafting_preview(state) 231 | end 232 | 233 | -- update the groups selection 234 | state.param.crafting_grouped_items = ui_tools.get_list_grouped(craftable_itemlist) 235 | update_group_selection(state, true) 236 | end 237 | 238 | ----------------------------------------------------- 239 | -- Build list matching the placed grid 240 | ----------------------------------------------------- 241 | local function update_from_grid(state, craft_grid, lookup_item) 242 | -- get all grid items for reference 243 | local player = state.location.rootState.location.player 244 | local reference_items = {} 245 | local items_hash = "" 246 | for _, stack in ipairs(craft_grid) do 247 | local name = stack:get_name() 248 | if name and name ~= "" then 249 | reference_items[name] = true 250 | items_hash=items_hash.."|"..name 251 | else 252 | items_hash=items_hash.."|empty" 253 | end 254 | end 255 | 256 | if items_hash ~= state.param.survival_grid_items_hash then 257 | state.param.survival_grid_items_hash = items_hash 258 | if next(reference_items) then 259 | -- update the grid with matched recipe items 260 | local recipes = crecipes.get_recipes_started_craft(player, craft_grid, reference_items) 261 | update_from_recipelist(state, recipes, lookup_item, true) -- replace_not_in_list=true 262 | end 263 | end 264 | end 265 | 266 | ----------------------------------------------------- 267 | -- Lookup for item lookup_item 268 | ----------------------------------------------------- 269 | local function do_lookup_item(state, playername, lookup_item) 270 | state.param.crafting_items_in_inventory = state.param.invobj:get_items() 271 | state.param.crafting_items_in_inventory[lookup_item] = true -- prefer in recipe preview 272 | -- get all craftable recipes with lookup-item as ingredient. Add recipes of lookup item to the list 273 | local recipes = crecipes.get_revealed_recipes_with_items(playername, {[lookup_item] = true }) 274 | update_from_recipelist(state, recipes, lookup_item) 275 | state.param.crafting_ui_controller:update_list_variant("lookup", lookup_item) 276 | end 277 | 278 | ----------------------------------------------------- 279 | -- Lookup inventory 280 | ----------------------------------------------------- 281 | local function create_lookup_inv(name) 282 | local player = minetest.get_player_by_name(name) 283 | local invname = name.."_crafting_inv" 284 | local plistname = "crafting_inv_lookup" 285 | local listname = "lookup" 286 | local pinv = player:get_inventory() 287 | local inv = minetest.get_inventory({type="detached", name=invname}) 288 | if not inv then 289 | inv = minetest.create_detached_inventory(invname, { 290 | allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) 291 | return 0 292 | end, 293 | allow_put = function(inv, listname, index, stack, player) 294 | if pinv:is_empty(plistname) then 295 | return 99 296 | else 297 | return 0 298 | end 299 | end, 300 | allow_take = function(inv, listname, index, stack, player) 301 | return 99 302 | end, 303 | on_put = function(inv, listname, index, stack, player) 304 | pinv:set_stack(plistname, index, stack) 305 | local name = player:get_player_name() 306 | local state = smart_inventory.get_page_state("crafting", name) 307 | do_lookup_item(state, name, stack:get_name()) 308 | 309 | -- we are outsite of usual smartfs processing. So trigger the formspec update byself 310 | state.location.rootState:show() 311 | 312 | -- put back 313 | minetest.after(1, function() 314 | -- Check maybe player is away from the game 315 | local player = minetest.get_player_by_name(name) 316 | if not player then 317 | return 318 | end 319 | 320 | -- Check the player did not removed item from lookup field 321 | local pinv = player:get_inventory() 322 | local inv = minetest.get_inventory({type="detached", name=invname}) 323 | local stack = pinv:get_stack(plistname, 1) 324 | 325 | -- put back 326 | local applied = pinv:add_item("main", stack) 327 | pinv:set_stack(plistname, 1, applied) 328 | inv:set_stack(listname, 1, applied) 329 | end) 330 | end, 331 | on_take = function(inv, listname, index, stack, player) 332 | pinv:set_stack(plistname, index, nil) 333 | end, 334 | }, name) 335 | end 336 | -- copy the item from player:listname inventory to the detached 337 | inv:set_size(listname, 1) 338 | pinv:set_size(plistname, 1) 339 | local stack = pinv:get_stack(plistname, 1) 340 | inv:set_stack(listname, 1, stack) 341 | end 342 | 343 | ----------------------------------------------------- 344 | -- Page layout definition 345 | ----------------------------------------------------- 346 | local function crafting_callback(state) 347 | local player = state.location.rootState.location.player 348 | 349 | -- build up UI controller 350 | local ui_controller = {} 351 | ui_controller.state = state 352 | ui_controller.player = minetest.get_player_by_name(state.location.rootState.location.player) 353 | state.param.crafting_ui_controller = ui_controller 354 | 355 | function ui_controller:set_ui_variant(new_ui) 356 | -- check if change needed 357 | if new_ui == self.toggle1 or new_ui == self.toggle2 then 358 | return 359 | end 360 | 361 | -- toggle show/hide elements 362 | if new_ui == 'list_small' then 363 | self.toggle1 = new_ui 364 | self.state:get("craft_img2"):setVisible(true) --rahmen oben 365 | self.state:get("lookup_icon"):setPosition(10, 4) 366 | self.state:get("lookup"):setPosition(10, 4) 367 | self.state:get("craftable"):setPosition(11, 4) 368 | self.state:get("btn_all"):setPosition(11, 4.5) 369 | self.state:get("btn_grid"):setPosition(11.5, 4.0) 370 | if smart_inventory.doc_items_mod then 371 | self.state:get("reveal_tipp"):setPosition(11.5, 4.5) 372 | end 373 | self.state:get("search"):setPosition(12.2, 4.5) 374 | self.state:get("search_bg"):setPosition(12, 4) 375 | self.state:get("search_btn"):setPosition(15.2, 4.2) 376 | self.state:get("info_tog"):setPosition(16.2, 4.2) 377 | 378 | self.state:get("buttons_grid_Bg"):setPosition(10, 5) 379 | self.state:get("buttons_grid_Bg"):setSize(8, 4) 380 | self.state:get("buttons_grid"):reset(10.25, 5.15, 8, 4) 381 | 382 | elseif new_ui == 'list_big' then 383 | self.toggle1 = new_ui 384 | self.state:get("craft_img2"):setVisible(false) --rahmen oben 385 | self.state:get("lookup_icon"):setPosition(10, 0) 386 | self.state:get("lookup"):setPosition(10, 0) 387 | self.state:get("craftable"):setPosition(11, 0) 388 | self.state:get("btn_all"):setPosition(11, 0.5) 389 | self.state:get("btn_grid"):setPosition(11.5, 0.0) 390 | if smart_inventory.doc_items_mod then 391 | self.state:get("reveal_tipp"):setPosition(11.5, 0.5) 392 | end 393 | self.state:get("search"):setPosition(12.2, 0.5) 394 | self.state:get("search_bg"):setPosition(12, 0) 395 | self.state:get("search_btn"):setPosition(15.2, 0.2) 396 | self.state:get("info_tog"):setPosition(16.2, 0.2) 397 | 398 | self.state:get("groups_sel"):setVisible(false) 399 | self.state:get("inf_area"):setVisible(false) 400 | 401 | self.state:get("buttons_grid_Bg"):setPosition(10, 1) 402 | self.state:get("buttons_grid_Bg"):setSize(8, 8) 403 | self.state:get("buttons_grid"):reset(10.25, 1.15, 8, 8) 404 | self.state:get("info_tog"):setId(3) 405 | else 406 | self.toggle2 = new_ui 407 | end 408 | 409 | if self.toggle1 == 'list_small' then 410 | if self.toggle2 == 'info' then 411 | self.state:get("groups_sel"):setVisible(false) 412 | self.state:get("inf_area"):setVisible(true) 413 | self.state:get("info_tog"):setId(1) 414 | elseif self.toggle2 == 'groups' then 415 | self.state:get("groups_sel"):setVisible(true) 416 | self.state:get("inf_area"):setVisible(false) 417 | self.state:get("info_tog"):setId(2) 418 | end 419 | end 420 | self:save() 421 | end 422 | 423 | function ui_controller:update_list_variant(list_variant, add_info) 424 | self.add_info = add_info 425 | -- reset group selection and search field on proposal mode change 426 | if self.list_variant ~= list_variant then 427 | self.list_variant = list_variant 428 | self.state:get("groups_sel"):setSelected(1) 429 | if list_variant ~= "search" then 430 | self.state:get("search"):setText("") 431 | end 432 | end 433 | 434 | -- auto-switch to the groups 435 | if list_variant == "lookup" or list_variant == "reveal_tipp" then 436 | state.param.crafting_ui_controller:set_ui_variant("info") 437 | else 438 | state.param.crafting_ui_controller:set_ui_variant("groups") 439 | end 440 | 441 | 442 | state:get("lookup_icon"):setBackground() 443 | state:get("search_bg"):setBackground() 444 | state:get("craftable"):setBackground() 445 | state:get("btn_grid"):setBackground() 446 | state:get("btn_all"):setBackground() 447 | if smart_inventory.doc_items_mod then 448 | state:get("reveal_tipp"):setBackground() 449 | end 450 | -- highlight the right button 451 | if list_variant == "lookup" then 452 | state:get("lookup_icon"):setBackground("halo.png") 453 | elseif list_variant == "search" then 454 | state:get("search_bg"):setBackground("halo.png") 455 | elseif list_variant == "craftable" then 456 | state:get("craftable"):setBackground("halo.png") 457 | elseif list_variant == "grid" then 458 | state:get("btn_grid"):setBackground("halo.png") 459 | elseif list_variant == "btn_all" then 460 | state:get("btn_all"):setBackground("halo.png") 461 | elseif list_variant == "reveal_tipp" then 462 | state:get("reveal_tipp"):setBackground("halo.png") 463 | end 464 | self:save() 465 | end 466 | 467 | function ui_controller:save() 468 | local savedata = minetest.deserialize(self.player:get_attribute("smart_inventory_settings")) or {} 469 | savedata.survival_list_variant = self.list_variant 470 | savedata.survival_toggle1 = self.toggle1 471 | savedata.survival_toggle2 = self.toggle2 472 | savedata.survival_lookup_item = self.lookup_item 473 | savedata.survival_add_info = self.add_info 474 | self.player:set_attribute("smart_inventory_settings", minetest.serialize(savedata)) 475 | end 476 | 477 | function ui_controller:restore() 478 | local savedata = minetest.deserialize(self.player:get_attribute("smart_inventory_settings")) or {} 479 | 480 | if savedata.survival_toggle1 then 481 | self:set_ui_variant(savedata.survival_toggle1) 482 | end 483 | if savedata.survival_toggle2 then 484 | self:set_ui_variant(savedata.survival_toggle2) 485 | end 486 | if savedata.survival_list_variant then 487 | if savedata.survival_list_variant == "search" then 488 | local ui_text = self.state:get(savedata.survival_list_variant) 489 | ui_text:setText(savedata.survival_add_info) 490 | ui_text:submit_key_enter("unused", self.state.location.rootState.location.player) 491 | elseif savedata.survival_list_variant == "lookup" then 492 | do_lookup_item(self.state, self.state.location.rootState.location.player, savedata.survival_add_info) 493 | else 494 | local ui_button = self.state:get(savedata.survival_list_variant) 495 | if ui_button then 496 | ui_button:submit("unused", self.state.location.rootState.location.player) 497 | end 498 | end 499 | else 500 | self.state:get("craftable"):submit("unused", self.state.location.rootState.location.player) 501 | self:set_ui_variant("groups") 502 | self:update_list_variant("craftable") 503 | end 504 | end 505 | 506 | -- set inventory style 507 | state:element("code", {name = "additional_code", code = 508 | "listcolors[#00000069;#5A5A5A;#141318;#30434C;#FFF]".. 509 | "listring[current_player;main]listring[current_player;craft]" 510 | }) 511 | 512 | --Inventorys / left site 513 | state:inventory(1, 5, 8, 4,"main") 514 | state:inventory(1.2, 0.2, 3, 3,"craft") 515 | state:inventory(4.3, 1.2, 1, 1,"craftpreview") 516 | state:background(1, 0, 4.5, 3.5, "img1", "menu_bg.png") 517 | 518 | 519 | -- crafting helper buttons 520 | local btn_ac1 = state:image_button(4.4, 0.3, 0.8, 0.8, "ac1", "", "smart_inventory_preview_to_crafting_field.png") 521 | btn_ac1:onClick(function(self, state, player) 522 | ui_tools.image_button_feedback(player, "crafting", "ac1") 523 | local grid = state:get("craft_preview"):getCraft() 524 | state.param.invobj:craft_item(grid) 525 | end) 526 | btn_ac1:setVisible(false) 527 | 528 | -- swap slots buttons 529 | state:image_button(0, 6, 1, 1, "swap1", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player) 530 | ui_tools.image_button_feedback(player, "crafting", "swap1") 531 | state.param.invobj:swap_row_to_top(2) 532 | end) 533 | state:image_button(0, 7, 1, 1, "swap2", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player) 534 | ui_tools.image_button_feedback(player, "crafting", "swap2") 535 | state.param.invobj:swap_row_to_top(3) 536 | end) 537 | state:image_button(0, 8, 1, 1, "swap3", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player) 538 | ui_tools.image_button_feedback(player, "crafting", "swap3") 539 | state.param.invobj:swap_row_to_top(4) 540 | end) 541 | 542 | ui_tools.create_trash_inv(state, player) 543 | state:image(8,9,1,1,"trash_icon","smart_inventory_trash.png") 544 | state:inventory(8, 9, 1, 1, "trash"):useDetached(player.."_trash_inv") 545 | 546 | local btn_compress = state:image_button(1, 3.8, 1, 1, "compress", "","smart_inventory_compress_button.png") 547 | btn_compress:setTooltip("Merge stacks with same items to get free place") 548 | btn_compress:onClick(function(self, state, player) 549 | ui_tools.image_button_feedback(player, "crafting", "compress") 550 | state.param.invobj:compress() 551 | end) 552 | 553 | local btn_sweep = state:image_button(2, 3.8, 1, 1, "clear", "", "smart_inventory_sweep_button.png") 554 | btn_sweep:setTooltip("Move all items from crafting grid back to inventory") 555 | btn_sweep:onClick(function(self, state, player) 556 | ui_tools.image_button_feedback(player, "crafting", "clear") 557 | state.param.invobj:sweep_crafting_inventory() 558 | end) 559 | 560 | -- recipe preview area 561 | smart_inventory.smartfs_elements.craft_preview(state, 6, 0, "craft_preview"):onButtonClicked(function(self, item, player) 562 | do_lookup_item(state, player, item) 563 | end) 564 | state:image(7,2.8,1,1,"cr_type_img",""):setVisible(false) 565 | state:label(7,3,"cr_type", "") 566 | local pr_prev_btn = state:button(6, 3, 1, 0.5, "preview_prev", "<<") 567 | pr_prev_btn:onClick(function(self, state, player) 568 | state.param.crafting_recipes_preview_selected = state.param.crafting_recipes_preview_selected -1 569 | update_crafting_preview(state) 570 | end) 571 | pr_prev_btn:setVisible(false) 572 | local pr_next_btn = state:button(8, 3, 1, 0.5, "preview_next", ">>") 573 | pr_next_btn:onClick(function(self, state, player) 574 | state.param.crafting_recipes_preview_selected = state.param.crafting_recipes_preview_selected +1 575 | update_crafting_preview(state) 576 | end) 577 | pr_next_btn:setVisible(false) 578 | 579 | -- (dynamic-1) group selection 580 | local group_sel = state:listbox(10.2, 0.15, 7.6, 3.6, "groups_sel",nil, true) 581 | group_sel:onClick(function(self, state, player) 582 | local selected = self:getSelectedItem() 583 | if selected then 584 | update_group_selection(state, false) 585 | end 586 | end) 587 | 588 | -- (dynamic-2) item preview area 589 | state:background(10.0, 0.1, 8, 3.8, "craft_img2", "minimap_overlay_square.png") 590 | local inf_area = state:view(6.4, 0.1, "inf_area") 591 | local inf_state = inf_area:getContainerState() 592 | inf_state:label(11.5,0.5,"info1", "") 593 | inf_state:label(11.5,1.0,"info2", "") 594 | inf_state:label(11.5,1.5,"info3", "") 595 | inf_state:item_image(10.2,0.3, 1, 1, "craft_result",nil):setVisible(false) 596 | if smart_inventory.doc_items_mod then 597 | local doc_btn = inf_state:image_button(10.4,2.3, 0.7, 0.7, "doc_btn","", "doc_button_icon_lores.png") 598 | doc_btn:setTooltip("Show documentation for revealed item") 599 | doc_btn:setVisible(false) 600 | doc_btn:onClick(function(self, state, player) 601 | local outitem = state:get("craft_result"):getImage() 602 | if outitem then 603 | for z in outitem:gmatch("[^%s]+") do 604 | if minetest.registered_items[z] then 605 | doc_addon.show(z, player) 606 | end 607 | break 608 | end 609 | end 610 | end) 611 | end 612 | inf_state:label(10.3, 3.25, "groups_label", "All") 613 | 614 | inf_state:listbox(12, 2, 5.7, 1.3, "item_groups",nil, true) 615 | inf_area:setVisible(false) 616 | 617 | -- Lookup 618 | create_lookup_inv(player) 619 | state:image(10, 4, 1, 1,"lookup_icon", "smart_inventory_lookup_field.png") 620 | local inv_lookup = state:inventory(10, 4.0, 1, 1,"lookup"):useDetached(player.."_crafting_inv") 621 | 622 | 623 | -- Get craftable by items in inventory 624 | local btn_craftable = state:image_button(11, 4, 0.5, 0.5, "craftable", "", "smart_inventory_craftable_button.png") 625 | btn_craftable:setTooltip("Show items crafteable by items in inventory") 626 | btn_craftable:onClick(function(self, state, player) 627 | state.param.crafting_items_in_inventory = state.param.invobj:get_items() 628 | local craftable = crecipes.get_recipes_craftable(player, state.param.crafting_items_in_inventory) 629 | update_from_recipelist(state, craftable) 630 | ui_controller:update_list_variant("craftable") 631 | end) 632 | 633 | local grid_btn = state:image_button(11.5, 4, 0.5, 0.5, "btn_grid", "", "smart_inventory_craftable_button.png") 634 | grid_btn:setTooltip("Search for recipes matching the grid") 635 | grid_btn:onClick(function(self, state, player) 636 | local player = state.location.rootState.location.player 637 | state.param.crafting_ui_controller:update_list_variant("grid") 638 | local craft_grid = state.param.invobj.inventory:get_list("craft") 639 | local ret_item = state.param.invobj.inventory:get_list("craftpreview")[1] 640 | update_from_grid(state, craft_grid, ret_item:get_name()) 641 | end) 642 | 643 | -- Get craftable by items in inventory 644 | local btn_all = state:image_button(11, 4.5, 0.5, 0.5, "btn_all", "", "smart_inventory_creative_button.png") 645 | if smart_inventory.doc_items_mod then 646 | btn_all:setTooltip("Show all already revealed items") 647 | else 648 | btn_all:setTooltip("Show all items") 649 | end 650 | btn_all:onClick(function(self, state, player) 651 | local all_revealed = ui_tools.filter_by_revealed(ui_tools.root_list_all, player, true) 652 | state.param.crafting_recipes_preview_selected = 1 653 | state.param.crafting_recipes_preview_listentry = all_revealed[1] or {} 654 | update_crafting_preview(state) 655 | state.param.crafting_grouped_items = ui_tools.get_list_grouped(all_revealed) 656 | 657 | update_group_selection(state, true) 658 | ui_controller:update_list_variant("btn_all") 659 | end) 660 | 661 | -- Reveal tipps button 662 | if smart_inventory.doc_items_mod then 663 | local reveal_button = state:image_button(11.5, 4.5, 0.5, 0.5, "reveal_tipp", "", "smart_inventory_reveal_tips_button.png") 664 | reveal_button:setTooltip("Show proposal what should be crafted to reveal more items") 665 | reveal_button:onClick(function(self, state, player) 666 | local all_revealed = ui_tools.filter_by_revealed(ui_tools.root_list_all, player) 667 | local top_revealed = ui_tools.filter_by_top_reveal(all_revealed, player) 668 | state.param.crafting_recipes_preview_selected = 1 669 | state.param.crafting_recipes_preview_listentry = top_revealed[1] or {} 670 | update_crafting_preview(state) 671 | state.param.crafting_grouped_items = ui_tools.get_list_grouped(top_revealed) 672 | 673 | update_group_selection(state, true) 674 | ui_controller:update_list_variant("reveal_tipp") 675 | end) 676 | end 677 | 678 | -- search 679 | state:background(12, 4, 4, 0.9, "search_bg", nil) --field background not usable 680 | local searchfield = state:field(12.2, 4.5, 3.4, 0.5, "search") 681 | searchfield:setCloseOnEnter(false) 682 | searchfield:onKeyEnter(function(self, state, player) 683 | local search_string = self:getText() 684 | if string.len(search_string) < 3 then 685 | return 686 | end 687 | 688 | local filtered_list = ui_tools.filter_by_searchstring(ui_tools.root_list_all, search_string, state.location.rootState.lang_code) 689 | filtered_list = ui_tools.filter_by_revealed(filtered_list, player) 690 | state.param.crafting_grouped_items = ui_tools.get_list_grouped(filtered_list) 691 | update_group_selection(state, true) 692 | ui_controller:update_list_variant("search", search_string) 693 | end) 694 | 695 | local search_button = state:button(15.2, 4.2, 0.8, 0.5, "search_btn", "Go") 696 | search_button:setTooltip("Perform search action") 697 | search_button:onClick(function(self, state, player) 698 | state:get("search"):submit_key_enter("", player) 699 | end) 700 | 701 | -- groups toggle 702 | local info_tog = state:toggle(16.2,4.2,1.8,0.5, "info_tog", {"Info", "Groups", "Hide"}) 703 | info_tog:onToggle(function(self, state, player) 704 | local id = self:getId() 705 | if id == 1 then 706 | state.param.crafting_ui_controller:set_ui_variant("list_small") 707 | state.param.crafting_ui_controller:set_ui_variant("info") 708 | elseif id == 2 then 709 | state.param.crafting_ui_controller:set_ui_variant("list_small") 710 | state.param.crafting_ui_controller:set_ui_variant("groups") 711 | elseif id == 3 then 712 | state.param.crafting_ui_controller:set_ui_variant("list_big") 713 | end 714 | end) 715 | 716 | -- craftable items grid 717 | state:background(10, 5, 8, 4, "buttons_grid_Bg", "minimap_overlay_square.png") 718 | local grid = smart_inventory.smartfs_elements.buttons_grid(state, 10.25, 5.15, 8 , 4, "buttons_grid", 0.75,0.75) 719 | grid:onClick(function(self, state, index, player) 720 | local listentry = state.param.crafting_craftable_list[index] 721 | if ui_controller.list_variant == "lookup" then 722 | do_lookup_item(state, state.location.rootState.location.player, listentry.item) 723 | else 724 | state.param.crafting_recipes_preview_selected = 1 725 | state.param.crafting_recipes_preview_listentry = listentry 726 | update_crafting_preview(state) 727 | end 728 | state.param.crafting_ui_controller:set_ui_variant("info") 729 | end) 730 | 731 | ui_controller:restore() 732 | end 733 | 734 | ----------------------------------------------------- 735 | -- Register page in smart_inventory 736 | ----------------------------------------------------- 737 | smart_inventory.register_page({ 738 | name = "crafting", 739 | tooltip = "Craft new items", 740 | icon = "smart_inventory_crafting_inventory_button.png", 741 | smartfs_callback = crafting_callback, 742 | sequence = 10 743 | }) 744 | 745 | ----------------------------------------------------- 746 | -- Use lookup for predict item 747 | ----------------------------------------------------- 748 | minetest.register_craft_predict(function(stack, player, old_craft_grid, craft_inv) 749 | local name = player:get_player_name() 750 | local state = smart_inventory.get_page_state("crafting", name) 751 | if not state then 752 | return 753 | end 754 | 755 | if state.param.crafting_ui_controller.list_variant ~= 'grid' then 756 | return 757 | end 758 | update_from_grid(state, old_craft_grid, stack:get_name()) 759 | state.location.rootState:show() 760 | end) 761 | -------------------------------------------------------------------------------- /pages/creative.lua: -------------------------------------------------------------------------------- 1 | local cache = smart_inventory.cache 2 | local ui_tools = smart_inventory.ui_tools 3 | 4 | ----------------------------------------------------- 5 | -- Update on group selection change 6 | ----------------------------------------------------- 7 | local function update_group_selection(state, changed_group) 8 | local grouped = state.param.creative_grouped_items 9 | local groups_sel1 = state:get("groups_sel1") 10 | local groups_sel2 = state:get("groups_sel2") 11 | local groups_sel3 = state:get("groups_sel3") 12 | local grid = state:get("buttons_grid") 13 | local outlist 14 | 15 | if state.param.creative_grouped_shape_items and 16 | next(state.param.creative_grouped_shape_items) then 17 | local group_info = {} 18 | group_info.name = "shape" 19 | group_info.cgroup = cache.cgroups["shape"] 20 | group_info.group_desc = "#01DF74> "..group_info.cgroup.group_desc 21 | group_info.items = state.param.creative_grouped_shape_items 22 | grouped["shape"] = group_info 23 | end 24 | 25 | -- update group 1 26 | if changed_group < 1 or not state.param.creative_group_list1 then 27 | state.param.creative_group_list1 = ui_tools.update_group_selection(grouped, groups_sel1, state.param.creative_group_list1) 28 | end 29 | 30 | local sel_id = groups_sel1:getSelected() 31 | if state.param.creative_group_list1[sel_id] == "all" 32 | or not state.param.creative_group_list1[sel_id] 33 | or not grouped[state.param.creative_group_list1[sel_id]] then 34 | outlist = grouped["all"].items 35 | groups_sel2:clearItems() 36 | groups_sel3:clearItems() 37 | else 38 | -- update group 2 39 | grouped = ui_tools.get_list_grouped(grouped[state.param.creative_group_list1[sel_id]].items) 40 | if changed_group < 2 or not state.param.creative_group_list2 then 41 | state.param.creative_group_list2 = ui_tools.update_group_selection(grouped, groups_sel2, state.param.creative_group_list2) 42 | end 43 | 44 | sel_id = groups_sel2:getSelected() 45 | if state.param.creative_group_list2[sel_id] == "all" 46 | or not state.param.creative_group_list2[sel_id] 47 | or not grouped[state.param.creative_group_list2[sel_id]] then 48 | outlist = grouped["all"].items 49 | groups_sel3:clearItems() 50 | else 51 | -- update group 3 52 | grouped = ui_tools.get_list_grouped(grouped[state.param.creative_group_list2[sel_id]].items) 53 | if changed_group < 3 or not state.param.creative_group_list3 then 54 | state.param.creative_group_list3 = ui_tools.update_group_selection(grouped, groups_sel3, state.param.creative_group_list3) 55 | end 56 | sel_id = groups_sel3:getSelected() 57 | outlist = grouped[state.param.creative_group_list3[sel_id]].items 58 | end 59 | end 60 | 61 | -- update grid list 62 | if outlist then 63 | table.sort(outlist, function(a,b) 64 | return a.sort_value < b.sort_value 65 | end) 66 | grid:setList(outlist) 67 | state.param.creative_outlist = outlist 68 | else 69 | grid:setList({}) 70 | end 71 | end 72 | 73 | ----------------------------------------------------- 74 | -- Page layout definition 75 | ----------------------------------------------------- 76 | local function creative_callback(state) 77 | 78 | local player = state.location.rootState.location.player 79 | 80 | -- build up UI controller 81 | local ui_controller = {} 82 | ui_controller.state = state 83 | state.param.creative_ui_controller = ui_controller 84 | ui_controller.player = minetest.get_player_by_name(state.location.rootState.location.player) 85 | 86 | function ui_controller:set_ui_variant(new_ui) 87 | -- check if change needed 88 | if new_ui == self.ui_toggle then 89 | return 90 | end 91 | 92 | -- toggle show/hide elements 93 | if new_ui == 'list_small' then 94 | self.ui_toggle = new_ui 95 | self.state:get("groups_sel1"):setSize(5.6, 3) 96 | self.state:get("groups_sel2"):setVisible(true) 97 | self.state:get("groups_sel3"):setVisible(true) 98 | self.state:get("buttons_grid"):reset(9.55, 3.75, 9.0 , 6.5) 99 | self.state:get("buttons_grid_bg"):setPosition(9.2, 3.5) 100 | self.state:get("buttons_grid_bg"):setSize(9.5, 6.5) 101 | self.state:get("btn_tog"):setId(1) 102 | elseif new_ui == 'list_big' then 103 | self.ui_toggle = new_ui 104 | self.state:get("groups_sel1"):setSize(7.8, 3) 105 | self.state:get("groups_sel2"):setVisible(false) 106 | self.state:get("groups_sel3"):setVisible(false) 107 | self.state:get("buttons_grid"):reset(9.55, 0.25, 9.0 , 10) 108 | self.state:get("buttons_grid_bg"):setPosition(9.2, 0) 109 | self.state:get("buttons_grid_bg"):setSize(9.5, 10) 110 | self.state:get("btn_tog"):setId(2) 111 | end 112 | self:save() 113 | end 114 | function ui_controller:save() 115 | local savedata = minetest.deserialize(self.player:get_attribute("smart_inventory_settings")) or {} 116 | savedata.creative_ui_toggle = self.ui_toggle 117 | self.player:set_attribute("smart_inventory_settings", minetest.serialize(savedata)) 118 | end 119 | 120 | function ui_controller:restore() 121 | local savedata = minetest.deserialize(self.player:get_attribute("smart_inventory_settings")) or {} 122 | 123 | if savedata.creative_ui_toggle then 124 | ui_controller:set_ui_variant(savedata.creative_ui_toggle) 125 | end 126 | end 127 | 128 | -- groups 1-3 129 | local group_sel1 = state:listbox(1, 0.15, 5.6, 3, "groups_sel1",nil, false) 130 | group_sel1:onClick(function(self, state, player) 131 | local selected = self:getSelectedItem() 132 | if selected then 133 | state:get("groups_sel2"):setSelected(1) 134 | state:get("groups_sel3"):setSelected(1) 135 | update_group_selection(state, 1) 136 | end 137 | end) 138 | 139 | local group_sel2 = state:listbox(7, 0.15, 5.6, 3, "groups_sel2",nil, false) 140 | group_sel2:onClick(function(self, state, player) 141 | local selected = self:getSelectedItem() 142 | if selected then 143 | state:get("groups_sel3"):setSelected(1) 144 | update_group_selection(state, 2) 145 | end 146 | end) 147 | 148 | local group_sel3 = state:listbox(13, 0.15, 5.6, 3, "groups_sel3",nil, false) 149 | group_sel3:onClick(function(self, state, player) 150 | local selected = self:getSelectedItem() 151 | if selected then 152 | update_group_selection(state, 3) 153 | end 154 | end) 155 | 156 | -- functions 157 | local searchfield = state:field(1.3, 4.1, 4.2, 0.5, "search") 158 | searchfield:setCloseOnEnter(false) 159 | searchfield:onKeyEnter(function(self, state, player) 160 | local search_string = self:getText() 161 | local lang_code = state.location.rootState.lang_code 162 | local filtered_list = ui_tools.filter_by_searchstring(ui_tools.root_list, search_string, lang_code) 163 | state.param.creative_grouped_items = ui_tools.get_list_grouped(filtered_list) 164 | filtered_list = ui_tools.filter_by_searchstring(ui_tools.root_list_shape, search_string, lang_code) 165 | state.param.creative_grouped_shape_items = filtered_list 166 | update_group_selection(state, 0) 167 | end) 168 | 169 | local search_button = state:button(5.0, 3.8, 1, 0.5, "search_btn", "Go") 170 | search_button:setTooltip("Perform search action") 171 | search_button:onClick(function(self, state, player) 172 | state:get("search"):submit_key_enter("", player) 173 | end) 174 | 175 | 176 | -- action mode toggle 177 | state:toggle(6, 3.8,1.5,0.5, "btn_tog_mode", {"Give 1", "Give stack"}) 178 | 179 | -- groups toggle 180 | local btn_toggle = state:toggle(7.5, 3.8,1.5,0.5, "btn_tog", {"Groups", "Hide"}) 181 | btn_toggle:onToggle(function(self, state, player) 182 | local id = self:getId() 183 | if id == 1 then 184 | state.param.creative_ui_controller:set_ui_variant("list_small") 185 | elseif id == 2 then 186 | state.param.creative_ui_controller:set_ui_variant("list_big") 187 | end 188 | end) 189 | 190 | -- craftable items grid 191 | state:background(9.2, 3.5, 9.5, 6.5, "buttons_grid_bg", "minimap_overlay_square.png") 192 | local grid = smart_inventory.smartfs_elements.buttons_grid(state, 9.55, 3.75, 9.0 , 6.5, "buttons_grid", 0.75,0.75) 193 | grid:onClick(function(self, state, index, player) 194 | local mode = state:get("btn_tog_mode"):getId() or 1 195 | local selected = ItemStack(state.param.creative_outlist[index].item) 196 | if mode == 1 then -- give 1 item 197 | state.param.invobj:add_item(selected) 198 | elseif mode == 2 then --give full stack 199 | selected:set_count(selected:get_stack_max()) 200 | state.param.invobj:add_sepearate_stack(selected) 201 | end 202 | end) 203 | 204 | -- inventory 205 | state:inventory(1, 5, 8, 4,"main") 206 | ui_tools.create_trash_inv(state, player) 207 | state:image(8,9,1,1,"trash_icon","smart_inventory_trash.png") 208 | state:element("code", {name = "trash_bg_code", code = "listcolors[#00000069;#5A5A5A;#141318;#30434C;#FFF]"}) 209 | state:inventory(8,9,1,1, "trash"):useDetached(player.."_trash_inv") 210 | 211 | -- swap slots buttons 212 | state:image_button(0, 6, 1, 1, "swap1", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player) 213 | ui_tools.image_button_feedback(player, "creative", "swap1") 214 | state.param.invobj:swap_row_to_top(2) 215 | end) 216 | state:image_button(0, 7, 1, 1, "swap2", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player) 217 | ui_tools.image_button_feedback(player, "creative", "swap2") 218 | state.param.invobj:swap_row_to_top(3) 219 | end) 220 | state:image_button(0, 8, 1, 1, "swap3", "", "smart_inventory_swapline_button.png"):onClick(function(self, state, player) 221 | ui_tools.image_button_feedback(player, "creative", "swap3") 222 | state.param.invobj:swap_row_to_top(4) 223 | end) 224 | 225 | -- trash button 226 | local trash_all = state:image_button(7,9,1,1, "trash_all", "", "smart_inventory_trash_all_button.png") 227 | trash_all:setTooltip("Trash all inventory content") 228 | trash_all:onClick(function(self, state, player) 229 | ui_tools.image_button_feedback(player, "creative", "trash_all") 230 | state.param.invobj:remove_all() 231 | end) 232 | 233 | -- save/restore buttons 234 | local btn_save1 = state:image_button(1,9,1,1, "save1", "", "smart_inventory_save1_button.png") 235 | btn_save1:setTooltip("Save inventory content to slot 1") 236 | btn_save1:onClick(function(self, state, player) 237 | ui_tools.image_button_feedback(player, "creative", "save1") 238 | state.param.invobj:save_to_slot(1) 239 | end) 240 | local btn_save2 = state:image_button(1.9,9,1,1, "save2", "", "smart_inventory_save2_button.png") 241 | btn_save2:setTooltip("Save inventory content to slot 2") 242 | btn_save2:onClick(function(self, state, player) 243 | ui_tools.image_button_feedback(player, "creative", "save2") 244 | state.param.invobj:save_to_slot(2) 245 | end) 246 | local btn_save3 = state:image_button(2.8,9,1,1, "save3", "", "smart_inventory_save3_button.png") 247 | btn_save3:setTooltip("Save inventory content to slot 3") 248 | btn_save3:onClick(function(self, state, player) 249 | ui_tools.image_button_feedback(player, "creative", "save3") 250 | state.param.invobj:save_to_slot(3) 251 | end) 252 | local btn_restore1 = state:image_button(4,9,1,1, "restore1", "", "smart_inventory_get1_button.png") 253 | btn_restore1:setTooltip("Restore inventory content from slot 1") 254 | btn_restore1:onClick(function(self, state, player) 255 | ui_tools.image_button_feedback(player, "creative", "restore1") 256 | state.param.invobj:restore_from_slot(1) 257 | end) 258 | local btn_restore2 = state:image_button(4.9,9,1,1, "restore2", "", "smart_inventory_get2_button.png") 259 | btn_restore2:setTooltip("Restore inventory content from slot 2") 260 | btn_restore2:onClick(function(self, state, player) 261 | ui_tools.image_button_feedback(player, "creative", "restore2") 262 | state.param.invobj:restore_from_slot(2) 263 | end) 264 | local btn_restore3 = state:image_button(5.8,9,1,1, "restore3", "", "smart_inventory_get3_button.png") 265 | btn_restore3:setTooltip("Restore inventory content from slot 3") 266 | btn_restore3:onClick(function(self, state, player) 267 | ui_tools.image_button_feedback(player, "creative", "restore3") 268 | state.param.invobj:restore_from_slot(3) 269 | end) 270 | 271 | -- fill with data 272 | state.param.creative_grouped_items = ui_tools.get_list_grouped(ui_tools.root_list) 273 | state.param.creative_grouped_shape_items = ui_tools.root_list_shape 274 | update_group_selection(state, 0) 275 | ui_controller:restore() 276 | end 277 | 278 | local function player_has_creative(state) 279 | return state.param.invobj:get_has_creative() 280 | end 281 | 282 | ----------------------------------------------------- 283 | -- Register page in smart_inventory 284 | ----------------------------------------------------- 285 | smart_inventory.register_page({ 286 | name = "creative", 287 | tooltip = "The creative way to get items", 288 | icon = "smart_inventory_creative_button.png", 289 | smartfs_callback = creative_callback, 290 | is_visible_func = player_has_creative, 291 | sequence = 15 292 | }) 293 | 294 | -- Redefinition for sfinv method maybe called from other mods 295 | if minetest.global_exists("sfinv") then 296 | function sfinv.set_player_inventory_formspec(player, context) 297 | local playername = player:get_player_name() 298 | 299 | local page_state = smart_inventory.get_page_state("creative", playername) 300 | if page_state then 301 | local state = page_state.location.parentState 302 | local has_creative = player_has_creative(state) 303 | state:get("creative_button"):setVisible(has_creative) 304 | if not has_creative then 305 | state:get("crafting_button"):submit(nil, playername) 306 | end 307 | state:show() 308 | end 309 | end 310 | end 311 | -------------------------------------------------------------------------------- /pages/doc.lua: -------------------------------------------------------------------------------- 1 | local doc_addon = smart_inventory.doc_addon 2 | 3 | if not smart_inventory.doc_items_mod then 4 | return 5 | end 6 | 7 | local COLOR_NOT_VIEWED = "#00FFFF" -- cyan 8 | local COLOR_VIEWED = "#FFFFFF" -- white 9 | local COLOR_HIDDEN = "#999999" -- gray 10 | local COLOR_ERROR = "#FF0000" -- red 11 | 12 | local function update_entry_list(state, selected_eid) 13 | local playername = state.location.rootState.location.player 14 | local category_id, category 15 | 16 | category_id = state:get("category"):getSelected() 17 | if category_id then 18 | category = doc_addon.get_category_list()[category_id] 19 | end 20 | if not category then 21 | return 22 | end 23 | 24 | local total = doc.get_entry_count(category.cid) 25 | local revealed = doc.get_revealed_count(playername, category.cid) 26 | local viewed = doc.get_viewed_count(playername, category.cid) 27 | local hidden = total - revealed 28 | local new = total - viewed - hidden 29 | 30 | state:get("lbl_category"):setText(category.data.def.description) 31 | state:get("lbl_viewed"):setText(minetest.colorize(COLOR_VIEWED,"Viewed: "..viewed.."/"..revealed)) 32 | state:get("lbl_hidden"):setText(minetest.colorize(COLOR_HIDDEN,"Hidden: "..hidden.."/"..total)) 33 | state:get("lbl_new"):setText(minetest.colorize(COLOR_NOT_VIEWED,"New: "..new)) 34 | local entries_box = state:get("entries") 35 | entries_box:clearItems() 36 | state.param.doc_entry_list = {} 37 | for _, edata in ipairs(category.entries) do 38 | local viewedprefix 39 | if doc.entry_revealed(playername, category.cid, edata.eid) then 40 | if doc.entry_viewed(playername, category.cid, edata.eid) then 41 | viewedprefix = COLOR_VIEWED 42 | else 43 | viewedprefix = COLOR_NOT_VIEWED 44 | end 45 | 46 | local name = edata.data.name 47 | if name == nil or name == "" then 48 | name = edata.eid 49 | end 50 | 51 | local idx = entries_box:addItem(viewedprefix..name) 52 | table.insert(state.param.doc_entry_list, edata) 53 | if selected_eid == edata.eid then 54 | entries_box:setSelected(idx) 55 | end 56 | end 57 | end 58 | end 59 | 60 | local function doc_callback(state) 61 | local playername = state.location.rootState.location.player 62 | 63 | state:label(0, 0, "lbl_category", "") 64 | state:background(0,0,20,0.5,"cat_bg", "halo.png") 65 | -- state:label(0, 0.5, "lbl_revealed", "") 66 | state:label(0, 0.5, "lbl_viewed", "") 67 | state:label(3, 0.5, "lbl_new", "") 68 | state:label(0, 1, "lbl_hidden", "") 69 | 70 | local category_box = state:listbox(0, 1.5, 4.8, 2, "category", nil, false) 71 | category_box:onClick(function(self, state, player) 72 | update_entry_list(state) 73 | end) 74 | 75 | state:listbox(0, 4, 4.8, 6, "entries", nil, false):onDoubleClick(function(self, state, player) 76 | local playername = state.location.rootState.location.player 77 | local selected = self:getSelected() 78 | local doc_entry = state.param.doc_entry_list[selected] 79 | if doc_entry then 80 | doc.mark_entry_as_viewed(playername, doc_entry.cid, doc_entry.eid) 81 | state:get("code").edata = doc_entry 82 | doc.data.players[playername].galidx = 1 83 | update_entry_list(state) 84 | end 85 | end) 86 | 87 | local codebox = state:element("code", { name = "code", code = "" }) 88 | codebox:onBuild(function(self) 89 | if self.edata then 90 | local state = self.root 91 | local playername = state.location.rootState.location.player 92 | self.data.code = "container[5.5,0]".. self.edata.cid_data.def.build_formspec(self.edata.data.data, playername).."container_end[]" 93 | else 94 | self.data.code = "" 95 | end 96 | end) 97 | 98 | -- to trigger the page update 99 | codebox:onSubmit(function(self, state) 100 | -- select the right category 101 | for idx, category in ipairs(doc_addon.get_category_list()) do 102 | if category.cid == self.edata.cid then 103 | state:get("category"):setSelected(idx) 104 | break 105 | end 106 | end 107 | 108 | -- update page for new category 109 | update_entry_list(state, self.edata.eid) 110 | end) 111 | 112 | state:onInput(function(state, fields, player) 113 | if fields.doc_button_gallery_prev then 114 | if doc.data.players[playername].galidx - doc.data.players[playername].galrows > 0 then 115 | doc.data.players[playername].galidx = doc.data.players[playername].galidx - doc.data.players[playername].galrows 116 | end 117 | 118 | elseif fields.doc_button_gallery_next then 119 | if doc.data.players[playername].galidx + doc.data.players[playername].galrows <= doc.data.players[playername].maxgalidx then 120 | doc.data.players[playername].galidx = doc.data.players[playername].galidx + doc.data.players[playername].galrows 121 | end 122 | end 123 | end) 124 | 125 | -- fill category table 126 | for _, category in ipairs(doc_addon.get_category_list()) do 127 | category_box:addItem(category.data.def.name) 128 | end 129 | end 130 | 131 | smart_inventory.register_page({ 132 | name = "doc", 133 | icon = "doc_awards_icon_generic.png", 134 | tooltip = "Ingame documentation", 135 | smartfs_callback = doc_callback, 136 | sequence = 30, 137 | on_button_click = update_entry_list, 138 | }) 139 | -------------------------------------------------------------------------------- /pages/player.lua: -------------------------------------------------------------------------------- 1 | smart_inventory.skins_mod = minetest.get_modpath("skinsdb") 2 | smart_inventory.armor_mod = minetest.get_modpath("3d_armor") 3 | smart_inventory.clothing_mod = minetest.get_modpath("clothing") 4 | 5 | if not smart_inventory.skins_mod and 6 | not smart_inventory.armor_mod and 7 | not smart_inventory.clothing_mod then 8 | return 9 | end 10 | 11 | local filter = smart_inventory.filter 12 | local cache = smart_inventory.cache 13 | local ui_tools = smart_inventory.ui_tools 14 | local txt = smart_inventory.txt 15 | 16 | local function update_grid(state, listname) 17 | local player_has_creative = state.param.invobj:get_has_creative() 18 | -- Update the users inventory grid 19 | local list = {} 20 | state.param["player_"..listname.."_list"] = list 21 | local name = state.location.rootState.location.player 22 | local player = minetest.get_player_by_name(name) 23 | local invlist_tab = {} 24 | if listname == "main" then 25 | local inventory = player:get_inventory() 26 | local invlist = inventory and inventory:get_list("main") 27 | table.insert(invlist_tab, invlist) 28 | else 29 | if smart_inventory.armor_mod then 30 | local inventory = minetest.get_inventory({type="detached", name=name.."_armor"}) 31 | local invlist = inventory and inventory:get_list("armor") 32 | if invlist then 33 | table.insert(invlist_tab, invlist) 34 | end 35 | end 36 | if smart_inventory.clothing_mod then 37 | local clothing_meta = player:get_attribute("clothing:inventory") 38 | state.param.player_clothing_data = clothing_meta and minetest.deserialize(clothing_meta) or {} 39 | local invlist = {} 40 | for i=1,6 do 41 | table.insert(invlist, ItemStack(state.param.player_clothing_data[i])) 42 | end 43 | table.insert(invlist_tab, invlist) 44 | end 45 | end 46 | 47 | local list_dedup = {} 48 | for _, invlist in ipairs(invlist_tab) do 49 | for stack_index, stack in ipairs(invlist) do 50 | local itemdef = stack:get_definition() 51 | local is_armor = false 52 | if itemdef then 53 | cache.add_item(itemdef) -- analyze groups in case of hidden armor 54 | if cache.citems[itemdef.name].cgroups["armor"] or cache.citems[itemdef.name].cgroups["clothing"] then 55 | local entry = {} 56 | for k, v in pairs(cache.citems[itemdef.name].ui_item) do 57 | entry[k] = v 58 | end 59 | entry.stack_index = stack_index 60 | local wear = stack:get_wear() 61 | if wear > 0 then 62 | entry.text = tostring(math.floor((1 - wear / 65535) * 100 + 0.5)).." %" 63 | end 64 | table.insert(list, entry) 65 | list_dedup[itemdef.name] = itemdef 66 | end 67 | end 68 | end 69 | end 70 | 71 | -- add all usable in creative available armor to the main list 72 | if listname == "main" and player_has_creative == true then 73 | if smart_inventory.armor_mod then 74 | for _, itemdef in pairs(cache.cgroups["armor"].items) do 75 | if not list_dedup[itemdef.name] and not itemdef.groups.not_in_creative_inventory then 76 | list_dedup[itemdef.name] = itemdef 77 | table.insert(list, cache.citems[itemdef.name].ui_item) 78 | end 79 | end 80 | end 81 | if smart_inventory.clothing_mod then 82 | for _, itemdef in pairs(cache.cgroups["clothing"].items) do 83 | if not list_dedup[itemdef.name] and not itemdef.groups.not_in_creative_inventory then 84 | list_dedup[itemdef.name] = itemdef 85 | table.insert(list, cache.citems[itemdef.name].ui_item) 86 | end 87 | end 88 | end 89 | end 90 | 91 | local grid = state:get(listname.."_grid") 92 | grid:setList(list) 93 | end 94 | 95 | local function update_selected_item(state, listentry) 96 | local name = state.location.rootState.location.player 97 | state.param.armor_selected_item = listentry or state.param.armor_selected_item 98 | listentry = state.param.armor_selected_item 99 | if not listentry then 100 | return 101 | end 102 | local i_list = state:get("i_list") 103 | i_list:clearItems() 104 | for _, groupdef in ipairs(ui_tools.get_tight_groups(cache.citems[listentry.itemdef.name].cgroups)) do 105 | i_list:addItem(groupdef.group_desc) 106 | end 107 | state:get("item_name"):setText(listentry.itemdef.description) 108 | state:get("item_image"):setImage(listentry.item) 109 | end 110 | 111 | local function update_page(state) 112 | local name = state.location.rootState.location.player 113 | local player_obj = minetest.get_player_by_name(name) 114 | local skin_obj = smart_inventory.skins_mod and skins.get_player_skin(player_obj) 115 | 116 | -- Update grid lines 117 | if smart_inventory.armor_mod or smart_inventory.clothing_mod then 118 | update_grid(state, "main") 119 | update_grid(state, "overlay") 120 | end 121 | 122 | -- Update preview area and armor informations list 123 | if smart_inventory.armor_mod then 124 | state:get("preview"):setImage(armor.textures[name].preview) 125 | state.location.parentState:get("player_button"):setImage(armor.textures[name].preview) 126 | local a_list = state:get("a_list") 127 | a_list:clearItems() 128 | for k, v in pairs(armor.def[name]) do 129 | local grouptext 130 | if k == "groups" then 131 | for gn, gv in pairs(v) do 132 | if txt and txt["armor:"..gn] then 133 | grouptext = txt["armor:"..gn] 134 | else 135 | grouptext = "armor:"..gn 136 | end 137 | if grouptext and gv ~= 0 then 138 | a_list:addItem(grouptext..": "..gv) 139 | end 140 | end 141 | else 142 | local is_physics = false 143 | for _, group in ipairs(armor.physics) do 144 | if group == k then 145 | is_physics = true 146 | break 147 | end 148 | end 149 | if is_physics then 150 | if txt and txt["physics:"..k] then 151 | grouptext = txt["physics:"..k] 152 | else 153 | grouptext = "physics:"..k 154 | end 155 | if grouptext and v ~= 1 then 156 | a_list:addItem(grouptext..": "..v) 157 | end 158 | else 159 | if txt and txt["armor:"..k] then 160 | grouptext = txt["armor:"..k] 161 | else 162 | grouptext = "armor:"..k 163 | end 164 | if grouptext and v ~= 0 then 165 | if k == "state" then 166 | a_list:addItem(grouptext..": "..tostring(math.floor((1 - v / armor.def[name].count / 65535) * 100 + 0.5)).." %") 167 | else 168 | a_list:addItem(grouptext..": "..v) 169 | end 170 | end 171 | end 172 | end 173 | end 174 | update_selected_item(state) 175 | elseif skin_obj then 176 | local skin_preview = skin_obj:get_preview() 177 | state.location.parentState:get("player_button"):setImage(skin_preview) 178 | state:get("preview"):setImage(skin_preview) 179 | elseif smart_inventory.clothing_mod then 180 | update_selected_item(state) 181 | state.location.parentState:get("player_button"):setImage('inventory_plus_clothing.png') 182 | state:get("preview"):setImage('blank.png') --TODO: build up clothing preview 183 | end 184 | 185 | -- Update skins list and skins info area 186 | if skin_obj then 187 | local m_name = skin_obj:get_meta_string("name") 188 | local m_author = skin_obj:get_meta_string("author") 189 | local m_license = skin_obj:get_meta_string("license") 190 | if m_name then 191 | state:get("skinname"):setText("Skin name: "..(skin_obj:get_meta_string("name"))) 192 | else 193 | state:get("skinname"):setText("") 194 | end 195 | if m_author then 196 | state:get("skinauthor"):setText("Author: "..(skin_obj:get_meta_string("author"))) 197 | else 198 | state:get("skinauthor"):setText("") 199 | end 200 | if m_license then 201 | state:get("skinlicense"):setText("License: "..(skin_obj:get_meta_string("license"))) 202 | else 203 | state:get("skinlicense"):setText("") 204 | end 205 | 206 | -- set the skins list 207 | state.param.skins_list = skins.get_skinlist_for_player(name) 208 | local cur_skin = skins.get_player_skin(player_obj) 209 | local skins_grid_data = {} 210 | local grid_skins = state:get("skins_grid") 211 | for idx, skin in ipairs(state.param.skins_list) do 212 | table.insert(skins_grid_data, { 213 | image = skin:get_preview(), 214 | tooltip = skin:get_meta_string("name"), 215 | is_button = true, 216 | size = { w = 0.87, h = 1.30 } 217 | }) 218 | if not state.param.skins_initial_page_adjusted and skin:get_key() == cur_skin:get_key() then 219 | grid_skins:setFirstVisible(idx - 19) --8x5 (grid size) / 2 -1 220 | state.param.skins_initial_page_adjusted = true 221 | end 222 | end 223 | grid_skins:setList(skins_grid_data) 224 | end 225 | end 226 | 227 | local function move_item_to_armor(state, item) 228 | local player_has_creative = state.param.invobj:get_has_creative() 229 | local name = state.location.rootState.location.player 230 | local player = minetest.get_player_by_name(name) 231 | local inventory = player:get_inventory() 232 | local armor_inv = minetest.get_inventory({type="detached", name=name.."_armor"}) 233 | 234 | -- get item to be moved to armor inventory 235 | local itemstack, itemname, itemdef 236 | if player_has_creative == true then 237 | itemstack = ItemStack(item.item) 238 | itemname = item.item 239 | else 240 | itemstack = inventory:get_stack("main", item.stack_index) 241 | itemname = itemstack:get_name() 242 | end 243 | itemdef = minetest.registered_items[itemname] 244 | local new_groups = {} 245 | for _, element in ipairs(armor.elements) do 246 | if itemdef.groups["armor_"..element] then 247 | new_groups["armor_"..element] = true 248 | end 249 | end 250 | 251 | -- remove all items with the same group 252 | local removed_items = {} 253 | for stack_index, stack in ipairs(armor_inv:get_list("armor")) do 254 | local old_def = stack:get_definition() 255 | if old_def then 256 | for groupname, groupdef in pairs(old_def.groups) do 257 | if new_groups[groupname] then 258 | table.insert(removed_items, stack) 259 | armor_inv:set_stack("armor", stack_index, {}) --take old 260 | minetest.detached_inventories[name.."_armor"].on_take(armor_inv, "armor", stack_index, stack, player) 261 | if armor_inv:set_stack("armor", stack_index, itemstack) then --put new 262 | minetest.detached_inventories[name.."_armor"].on_put(armor_inv, "armor", stack_index, itemstack, player) 263 | itemstack = ItemStack("") 264 | end 265 | end 266 | end 267 | end 268 | if stack:is_empty() and not itemstack:is_empty() then 269 | if armor_inv:set_stack("armor", stack_index, itemstack) then 270 | minetest.detached_inventories[name.."_armor"].on_put(armor_inv, "armor", stack_index, itemstack, player) 271 | itemstack = ItemStack("") 272 | end 273 | end 274 | end 275 | 276 | -- handle put backs in non-creative to not lost items 277 | if player_has_creative == false then 278 | inventory:set_stack("main", item.stack_index, itemstack) 279 | for _, stack in ipairs(removed_items) do 280 | stack = inventory:add_item("main", stack) 281 | if not stack:is_empty() then 282 | armor_inv:add_item("armor", stack) 283 | end 284 | end 285 | end 286 | end 287 | 288 | local function move_item_to_clothing(state, item) 289 | local name = state.location.rootState.location.player 290 | local player = minetest.get_player_by_name(name) 291 | local inventory = player:get_inventory() 292 | 293 | local clothes = state.param.player_clothing_data 294 | local clothes_ordered = {} 295 | 296 | for i=1, 6 do 297 | if clothes[i] then 298 | table.insert(clothes_ordered, clothes[i]) 299 | end 300 | end 301 | 302 | if #clothes_ordered < 6 then 303 | table.insert(clothes_ordered, item.item) 304 | player:set_attribute("clothing:inventory", minetest.serialize(clothes_ordered)) 305 | clothing:set_player_clothing(player) 306 | state.param.player_clothing_data = clothes_ordered 307 | -- handle put backs in non-creative to not lost items 308 | if player_has_creative == false then 309 | local itemstack = inventory:get_stack("main", item.stack_index) 310 | itemstack:take_item() 311 | inventory:set_stack("main", item.stack_index, itemstack) 312 | end 313 | end 314 | end 315 | 316 | local function move_item_to_inv(state, item) 317 | local player_has_creative = state.param.invobj:get_has_creative() 318 | local name = state.location.rootState.location.player 319 | local player = minetest.get_player_by_name(name) 320 | local inventory = player:get_inventory() 321 | 322 | if cache.cgroups["armor"] and cache.cgroups["armor"].items[item.item] then 323 | local armor_inv = minetest.get_inventory({type="detached", name=name.."_armor"}) 324 | local itemstack = armor_inv:get_stack("armor", item.stack_index) 325 | if player_has_creative == true then 326 | -- trash armor item in creative 327 | itemstack = ItemStack("") 328 | else 329 | itemstack = inventory:add_item("main", itemstack) 330 | end 331 | armor_inv:set_stack("armor", item.stack_index, itemstack) 332 | minetest.detached_inventories[name.."_armor"].on_take(armor_inv, "armor", item.stack_index, itemstack, player) 333 | 334 | elseif cache.cgroups["clothing"] and cache.cgroups["clothing"].items[item.item] then 335 | local clothes = state.param.player_clothing_data 336 | 337 | if player_has_creative ~= true and clothes[item.stack_index] then 338 | local itemstack = inventory:add_item("main", ItemStack(clothes[item.stack_index])) 339 | if itemstack:is_empty() then 340 | clothes[item.stack_index] = nil 341 | end 342 | else 343 | clothes[item.stack_index] = nil 344 | end 345 | player:set_attribute("clothing:inventory", minetest.serialize(clothes)) 346 | clothing:set_player_clothing(player) 347 | end 348 | 349 | end 350 | 351 | local function player_callback(state) 352 | local name = state.location.rootState.location.player 353 | state:background(0, 1.2, 6, 6.6, "it_bg", "smart_inventory_background_border.png") 354 | state:item_image(0.8, 1.5,2,2,"item_image","") 355 | state:label(2.5,1.2,"item_name", "") 356 | state:listbox(0.8,3.3,5.1,4,"i_list", nil, true) 357 | 358 | state:background(6.2, 1.2, 6, 6.6, "pl_bg", "smart_inventory_background_border.png") 359 | state:image(6.7,1.7,2,4,"preview","") 360 | state:listbox(8.6,1.7,3.5,3,"a_list", nil, true) 361 | state:label(6.7,5.5,"skinname","") 362 | state:label(6.7,6.0,"skinauthor", "") 363 | state:label(6.7,6.5, "skinlicense", "") 364 | 365 | state:background(0, 0, 20, 1, "top_bg", "halo.png") 366 | state:background(0, 8, 20, 2, "bottom_bg", "halo.png") 367 | if smart_inventory.armor_mod or smart_inventory.clothing_mod then 368 | local grid_overlay = smart_inventory.smartfs_elements.buttons_grid(state, 0, 0, 20, 1, "overlay_grid") 369 | 370 | grid_overlay:onClick(function(self, state, index, player) 371 | if state.param.player_overlay_list[index] then 372 | update_selected_item(state, state.param.player_overlay_list[index]) 373 | move_item_to_inv(state, state.param.player_overlay_list[index]) 374 | update_page(state) 375 | end 376 | end) 377 | 378 | local grid_main = smart_inventory.smartfs_elements.buttons_grid(state, 0, 8, 20, 2, "main_grid") 379 | grid_main:onClick(function(self, state, index, player) 380 | update_selected_item(state, state.param.player_main_list[index]) 381 | local item = state.param.player_main_list[index] 382 | if cache.citems[item.item].cgroups["armor"] then 383 | move_item_to_armor(state, state.param.player_main_list[index]) 384 | elseif cache.citems[item.item].cgroups["clothing"] then 385 | move_item_to_clothing(state, state.param.player_main_list[index]) 386 | end 387 | update_page(state) 388 | end) 389 | end 390 | 391 | if smart_inventory.skins_mod then 392 | local player_obj = minetest.get_player_by_name(name) 393 | -- Skins Grid 394 | local grid_skins = smart_inventory.smartfs_elements.buttons_grid(state, 12.9, 1.5, 7 , 7, "skins_grid", 0.80, 1.20) 395 | state:background(12.4, 1.2, 7.5 , 6.6, "bg_skins", "smart_inventory_background_border.png") 396 | grid_skins:onClick(function(self, state, index, player) 397 | local cur_skin = state.param.skins_list[index] 398 | if state.location.rootState.location.type ~= "inventory" and cur_skin._key:sub(1,17) == "character_creator" then 399 | state.location.rootState.obsolete = true -- other screen appears, obsolete the inventory session 400 | end 401 | skins.set_player_skin(player_obj, cur_skin) 402 | if smart_inventory.armor_mod then 403 | armor.textures[name].skin = cur_skin:get_texture() 404 | armor:set_player_armor(player_obj) 405 | end 406 | update_page(state) 407 | end) 408 | end 409 | 410 | -- not visible update plugin for updates from outsite (API) 411 | state:element("code", { name = "update_hook" }):onSubmit(function(self, state) 412 | update_page(state) 413 | state.location.rootState:show() 414 | end) 415 | 416 | update_page(state) 417 | end 418 | 419 | smart_inventory.register_page({ 420 | name = "player", 421 | icon = "player.png", 422 | tooltip = "Customize yourself", 423 | smartfs_callback = player_callback, 424 | sequence = 20, 425 | on_button_click = update_page 426 | }) 427 | 428 | -- register callback in 3d_armor for updates 429 | if smart_inventory.armor_mod and armor.register_on_update then 430 | 431 | local function submit_update_hook(player) 432 | local name = player:get_player_name() 433 | local state = smart_inventory.get_page_state("player", name) 434 | if state then 435 | state:get("update_hook"):submit() 436 | end 437 | end 438 | 439 | armor:register_on_update(submit_update_hook) 440 | 441 | -- There is no callback in 3d_armor for wear change in on_hpchange implementation 442 | minetest.register_on_player_hpchange(function(player, hp_change) 443 | minetest.after(0, submit_update_hook, player) 444 | end) 445 | end 446 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/screenshot.png -------------------------------------------------------------------------------- /screenshot_crafting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/screenshot_crafting.png -------------------------------------------------------------------------------- /screenshot_creative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/screenshot_creative.png -------------------------------------------------------------------------------- /screenshot_doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/screenshot_doc.png -------------------------------------------------------------------------------- /screenshot_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/screenshot_player.png -------------------------------------------------------------------------------- /settingtypes.txt: -------------------------------------------------------------------------------- 1 | #If enabled, the mod will show alternative human readable filterstrings if available. 2 | smart_inventory_friendly_group_names (Show “friendly” filter grouping names) bool true 3 | 4 | #List of groups defined for special handling of "Shaped nodes" (Comma separated). 5 | #Items in this groups ignores the "not_in_inventory" group and are moved to separate "Shaped" category 6 | smart_inventory_shaped_groups (List of groups to be handled as separate) string carpet,door,fence,stair,slab,wall,micro,panel,slope,dye 7 | 8 | #If enabled, the the mod does not replace other inventory mods. 9 | #The functionality is provided in a workbench. 10 | smart_inventory_workbench_mode (Use workbench instead of players inventory) bool false 11 | -------------------------------------------------------------------------------- /textures/README: -------------------------------------------------------------------------------- 1 | The background border image goes where the skin button would normally go. 2 | Inside the bordered area should be put the fron texture of a skins head. -------------------------------------------------------------------------------- /textures/smart_inventory_background_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_background_border.png -------------------------------------------------------------------------------- /textures/smart_inventory_compress_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_compress_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_craftable_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_craftable_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_crafting_inventory_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_crafting_inventory_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_creative_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_creative_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_exit_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_exit_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_furnace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_furnace.png -------------------------------------------------------------------------------- /textures/smart_inventory_get1_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_get1_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_get2_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_get2_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_get3_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_get3_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_left_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_left_arrow.png -------------------------------------------------------------------------------- /textures/smart_inventory_lookup_field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_lookup_field.png -------------------------------------------------------------------------------- /textures/smart_inventory_preview_to_crafting_field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_preview_to_crafting_field.png -------------------------------------------------------------------------------- /textures/smart_inventory_reveal_tips_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_reveal_tips_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_right_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_right_arrow.png -------------------------------------------------------------------------------- /textures/smart_inventory_save1_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_save1_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_save2_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_save2_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_save3_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_save3_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_swapline_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_swapline_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_sweep_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_sweep_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_trash.png -------------------------------------------------------------------------------- /textures/smart_inventory_trash_all_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_trash_all_button.png -------------------------------------------------------------------------------- /textures/smart_inventory_workbench_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_workbench_front.png -------------------------------------------------------------------------------- /textures/smart_inventory_workbench_sides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_workbench_sides.png -------------------------------------------------------------------------------- /textures/smart_inventory_workbench_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/smart_inventory/f790cc8bb3da2eb7c02ee61f7053bee085073994/textures/smart_inventory_workbench_top.png -------------------------------------------------------------------------------- /ui_tools.lua: -------------------------------------------------------------------------------- 1 | local cache = smart_inventory.cache 2 | local crecipes = smart_inventory.crecipes 3 | local txt = smart_inventory.txt 4 | local doc_addon = smart_inventory.doc_addon 5 | 6 | local ui_tools = {} 7 | ----------------------------------------------------- 8 | -- Group item list and prepare for output 9 | ----------------------------------------------------- 10 | -- Parameters: 11 | -- grouped: grouped items list (result of cache.get_list_grouped) 12 | -- groups_sel: smartfs Element (table) that should contain the groups 13 | -- groups_tab: shadow table that with items per group that will be updated in this method 14 | -- Return: updated groups_tab 15 | 16 | 17 | function ui_tools.update_group_selection(grouped, groups_sel, groups_tab) 18 | -- save old selection 19 | local sel_id = groups_sel:getSelected() 20 | local sel_grp 21 | if sel_id and groups_tab then 22 | sel_grp = groups_tab[sel_id] 23 | end 24 | 25 | -- sort the groups 26 | local group_sorted = {} 27 | for _, group in pairs(grouped) do 28 | table.insert(group_sorted, group) 29 | end 30 | 31 | table.sort(group_sorted, function(a,b) 32 | local sort_fixed_order = { 33 | ["all"] = 5, -- at the begin 34 | ["other"] = 80, -- at the end 35 | ["shape"] = 90, --at the end 36 | } 37 | local aval = sort_fixed_order[a.name] or 10 38 | local bval = sort_fixed_order[b.name] or 10 39 | if aval ~= bval then 40 | return aval < bval 41 | else 42 | return a.name < b.name 43 | end 44 | end) 45 | 46 | -- apply groups to the groups_sel table and to the new groups_tab 47 | groups_sel:clearItems() 48 | groups_tab = {} 49 | for _, group in ipairs(group_sorted) do 50 | if #group.items > 0 then 51 | local idx = groups_sel:addItem(group.group_desc.." ("..#group.items..")") 52 | groups_tab[idx] = group.name 53 | if sel_grp == group.name then 54 | sel_id = idx 55 | end 56 | end 57 | end 58 | 59 | -- restore selection 60 | if not groups_tab[sel_id] then 61 | sel_id = 1 62 | end 63 | groups_sel:setSelected(sel_id) 64 | 65 | return groups_tab 66 | end 67 | 68 | 69 | ----------------------------------------------------- 70 | -- Create trash inventory 71 | ----------------------------------------------------- 72 | function ui_tools.create_trash_inv(state, name) 73 | local player = minetest.get_player_by_name(name) 74 | local invname = name.."_trash_inv" 75 | local listname = "trash" 76 | local inv = minetest.get_inventory({type="detached", name=invname}) 77 | if not inv then 78 | inv = minetest.create_detached_inventory(invname, { 79 | allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) 80 | return 0 81 | end, 82 | allow_put = function(inv, listname, index, stack, player) 83 | return 99 84 | end, 85 | allow_take = function(inv, listname, index, stack, player) 86 | return 99 87 | end, 88 | on_move = function(inv, from_list, from_index, to_list, to_index, count, player) 89 | end, 90 | on_put = function(inv, listname, index, stack, player) 91 | minetest.after(1, function(stack) 92 | inv:set_stack(listname, index, nil) 93 | end) 94 | end, 95 | on_take = function(inv, listname, index, stack, player) 96 | inv:set_stack(listname, index, nil) 97 | end, 98 | }, name) 99 | end 100 | inv:set_size(listname, 1) 101 | end 102 | 103 | 104 | ----------------------------------------------------- 105 | -- Filter a list by search string 106 | ----------------------------------------------------- 107 | function ui_tools.filter_by_searchstring(list, search_string, lang_code) 108 | local filtered_list = {} 109 | search_string = search_string:lower() 110 | for _, entry in ipairs(list) do 111 | local def = minetest.registered_items[entry.item] 112 | local description = def.description 113 | if lang_code then 114 | description = minetest.get_translated_string(lang_code, description) 115 | end 116 | if string.find(description:lower(), search_string) or 117 | string.find(def.name:lower(), search_string) then 118 | table.insert(filtered_list, entry) 119 | else 120 | for _, cgroup in pairs(entry.citem.cgroups) do 121 | if cgroup.keyword then 122 | if string.find(cgroup.keyword:lower():gsub("_", ":"), search_string:gsub("_", ":"))then 123 | table.insert(filtered_list, entry) 124 | break 125 | end 126 | end 127 | if cgroup.group_desc then 128 | local group_desc =txt[cgroup.group_desc] or cgroup.group_desc 129 | if string.find(group_desc:lower(), search_string)then 130 | table.insert(filtered_list, entry) 131 | break 132 | end 133 | end 134 | end 135 | end 136 | end 137 | return filtered_list 138 | end 139 | 140 | ----------------------------------------------------- 141 | -- Get all revealed items available 142 | ----------------------------------------------------- 143 | function ui_tools.filter_by_revealed(list, playername, by_item_only) 144 | if not smart_inventory.doc_items_mod then 145 | return list 146 | end 147 | local revealed_items_cache = {} 148 | local filtered_list = {} 149 | for _, entry in ipairs(list) do 150 | -- check recipes 151 | local revealed_by_recipe = false 152 | if by_item_only ~= true and 153 | cache.citems[entry.item] and 154 | cache.citems[entry.item].in_output_recipe then 155 | for _, recipe in ipairs(cache.citems[entry.item].in_output_recipe) do 156 | if crecipes.crecipes[recipe]:is_revealed(playername, revealed_items_cache) then 157 | revealed_by_recipe = true 158 | break 159 | end 160 | end 161 | end 162 | if revealed_by_recipe or doc_addon.is_revealed_item(entry.item, playername) then 163 | table.insert(filtered_list, entry) 164 | end 165 | end 166 | return filtered_list 167 | end 168 | 169 | ----------------------------------------------------- 170 | -- Get all revealed items available 171 | ----------------------------------------------------- 172 | function ui_tools.filter_by_top_reveal(list, playername) 173 | -- check the list for not revealed only. Create search index 174 | local craftable_only = {} 175 | for _, entry in ipairs(list) do 176 | -- only not revealed could be in tipp 177 | if not doc_addon.is_revealed_item(entry.item, playername) then 178 | craftable_only[entry.item] = entry 179 | end 180 | end 181 | 182 | local rating_tab = {} 183 | local revealed_items_cache = {} 184 | 185 | for itemname, entry in pairs(craftable_only) do 186 | -- Check all recipes 187 | --print("check", itemname) 188 | local rating_value = 0 189 | -- Check all items 190 | for _, recipe in ipairs(cache.citems[itemname].in_craft_recipe) do 191 | if crecipes.crecipes[recipe] then 192 | local crecipe = crecipes.crecipes[recipe] 193 | if not doc_addon.is_revealed_item(crecipe.out_item.name, playername) then 194 | --print("check recipe out:", crecipe.out_item.name) 195 | 196 | local revealed_by_other_recipe = false 197 | for _, recipe in ipairs(cache.citems[crecipe.out_item.name].in_output_recipe) do 198 | if crecipes.crecipes[recipe]:is_revealed(playername, revealed_items_cache) then 199 | revealed_by_other_recipe = true 200 | break 201 | end 202 | end 203 | 204 | if not revealed_by_other_recipe then 205 | for recipe_itemname, iteminfo in pairs(crecipe._items) do 206 | -- in recipe 207 | if recipe_itemname == itemname or minetest.registered_aliases[recipe_itemname] == itemname then 208 | rating_value = rating_value + 1 209 | --print("by name", recipe_itemname, iteminfo.items[recipe_itemname].name) 210 | elseif recipe_itemname:sub(1, 6) == "group:" and iteminfo.items[itemname] then 211 | local is_revealed = false 212 | for alt_itemname, _ in pairs (iteminfo.items) do 213 | if doc_addon.is_revealed_item(alt_itemname, playername) then 214 | is_revealed = true 215 | break 216 | end 217 | end 218 | if not is_revealed then 219 | --print("by group", recipe_itemname, itemname) 220 | rating_value = rating_value + 1 221 | end 222 | end 223 | end 224 | end 225 | end 226 | end 227 | --print("rating", itemname, rating_value) 228 | rating_tab[itemname] = (rating_tab[itemname] or 0) + rating_value 229 | end 230 | end 231 | 232 | -- prepare output list 233 | local sorted_rating = {} 234 | for itemname, rating in pairs(rating_tab) do 235 | table.insert(sorted_rating, {itemname = itemname, rating = rating}) 236 | end 237 | table.sort(sorted_rating, function(a,b) return a.rating > b.rating end) 238 | 239 | local out_count = 0 240 | local filtered_list = {} 241 | local top_rating = 0 242 | for _, v in ipairs(sorted_rating) do 243 | -- top 10 but show all with the same rating 244 | if out_count < 20 or v.rating == top_rating then 245 | top_rating = v.rating 246 | local entry = craftable_only[v.itemname] 247 | if v.rating > 0 then 248 | entry = {} 249 | for kk, vv in pairs(craftable_only[v.itemname]) do 250 | entry[kk] = vv 251 | end 252 | entry.text = v.rating 253 | end 254 | table.insert(filtered_list, entry) 255 | out_count = out_count + 1 256 | else 257 | break 258 | end 259 | end 260 | return filtered_list 261 | end 262 | 263 | ----------------------------------------------------- 264 | -- Select tight groups only to display info about item 265 | ----------------------------------------------------- 266 | function ui_tools.get_tight_groups(cgroups) 267 | local out_list = {} 268 | for group1, groupdef1 in pairs(cgroups) do 269 | if groupdef1.keyword then 270 | out_list[group1] = groupdef1 271 | for group2, groupdef2 in pairs(out_list) do 272 | if string.len(group1) > string.len(group2) and 273 | string.sub(group1,1,string.len(group2)) == group2 then 274 | -- group2 is top-group of group1. Remove the group2 275 | out_list[group2] = nil 276 | elseif string.len(group1) < string.len(group2) and 277 | string.sub(group2,1,string.len(group1)) == group1 then 278 | -- group2 is top-group of group1. Remove the group2 279 | out_list[group1] = nil 280 | end 281 | end 282 | end 283 | end 284 | local out_list_sorted = {} 285 | for group, groupdef in pairs(out_list) do 286 | table.insert(out_list_sorted, groupdef) 287 | end 288 | table.sort(out_list_sorted, function(a,b) 289 | return a.group_desc < b.group_desc 290 | end) 291 | return out_list_sorted 292 | end 293 | 294 | ----------------------------------------------------- 295 | -- Sort items to groups and decide which groups should be displayed 296 | ----------------------------------------------------- 297 | function ui_tools.get_list_grouped(itemtable) 298 | local grouped = {} 299 | -- sort the entries to groups 300 | for _, entry in ipairs(itemtable) do 301 | if cache.citems[entry.item] then 302 | for _, group in pairs(cache.citems[entry.item].cgroups) do 303 | if not grouped[group.name] then 304 | local group_info = {} 305 | group_info.name = group.name 306 | group_info.cgroup = cache.cgroups[group.name] 307 | group_info.items = {} 308 | grouped[group.name] = group_info 309 | end 310 | table.insert(grouped[group.name].items, entry) 311 | end 312 | end 313 | end 314 | 315 | -- magic to calculate relevant groups 316 | local itemcount = #itemtable 317 | local best_group_count = itemcount ^(1/3) 318 | local best_group_size = (itemcount / best_group_count) * 1.5 319 | best_group_count = math.floor(best_group_count) 320 | local sorttab = {} 321 | 322 | for k,v in pairs(grouped) do 323 | if #v.items < 3 or #v.items >= itemcount - 3 then 324 | grouped[k] = nil 325 | else 326 | v.group_size = #v.items 327 | v.unique_count = #v.items 328 | v.best_group_size = best_group_size 329 | v.diff = math.abs(v.group_size - v.best_group_size) 330 | table.insert(sorttab, v) 331 | end 332 | end 333 | 334 | local outtab = {} 335 | local assigned_items = {} 336 | if best_group_count > 0 then 337 | for i = 1, best_group_count do 338 | -- sort by best size 339 | table.sort(sorttab, function(a,b) 340 | return a.diff < b.diff 341 | end) 342 | 343 | local sel = sorttab[1] 344 | 345 | if not sel then 346 | break 347 | end 348 | outtab[sel.name] = { 349 | name = sel.name, 350 | group_desc = sel.cgroup.group_desc, 351 | items = sel.items 352 | } 353 | table.remove(sorttab, 1) 354 | 355 | 356 | for _, item in ipairs(sel.items) do 357 | assigned_items[item.item] = true 358 | -- update the not selected groups 359 | for _, group in pairs(cache.citems[item.item].cgroups) do 360 | if group.name ~= sel.name then 361 | local u = grouped[group.name] 362 | if u and u.unique_count and u.group_size > 0 then 363 | u.unique_count = u.unique_count-1 364 | if (u.group_size < u.best_group_size) or 365 | (u.group_size - u.best_group_size) < (u.best_group_size - u.unique_count) then 366 | sel.diff = u.best_group_size - u.unique_count 367 | end 368 | end 369 | end 370 | end 371 | end 372 | 373 | for idx = #sorttab, 1, -1 do 374 | if sorttab[idx].unique_count < 3 or 375 | ( sel.cgroup.parent and sel.cgroup.parent.name == sorttab[idx].name ) or 376 | ( sel.cgroup.childs and sel.cgroup.childs[sorttab[idx].name] ) 377 | then 378 | grouped[sorttab[idx].name] = nil 379 | table.remove(sorttab, idx) 380 | end 381 | end 382 | end 383 | end 384 | 385 | -- fill other group 386 | local other = {} 387 | for _, item in ipairs(itemtable) do 388 | if not assigned_items[item.item] then 389 | table.insert(other, item) 390 | end 391 | end 392 | 393 | -- default groups 394 | outtab.all = {} 395 | outtab.all.name = "all" 396 | outtab.all.items = itemtable 397 | 398 | outtab.other = {} 399 | outtab.other.name = "other" 400 | outtab.other.items = other 401 | 402 | if txt then 403 | outtab.all.group_desc = txt[outtab.all.name] or "all" 404 | outtab.other.group_desc = txt[outtab.other.name] or "other" 405 | else 406 | outtab.all.group_desc = "all" 407 | outtab.other.group_desc = "other" 408 | end 409 | 410 | return outtab 411 | end 412 | 413 | 414 | local function unifieddyes_sort_order() end 415 | if minetest.global_exists("unifieddyes") then 416 | function unifieddyes_sort_order(entry) 417 | local ret = unifieddyes.getpaletteidx(entry.item, "extended") 418 | if ret then 419 | local ret2 = string.format("%02X", ret) 420 | return 'dye '..ret2 421 | end 422 | end 423 | end 424 | 425 | local function armor_sort_order(entry) end 426 | if minetest.global_exists("armor") then 427 | function armor_sort_order(entry) 428 | if not entry.citem.cgroups["armor"] then 429 | return 430 | end 431 | local split = entry.item:split("_") 432 | return "armor "..split[#split] .. entry.item 433 | end 434 | end 435 | 436 | ----------------------------------------------------- 437 | -- Prepare root lists for all users 438 | ----------------------------------------------------- 439 | local function prepare_root_lists() 440 | ui_tools.root_list = {} 441 | ui_tools.root_list_shape = {} 442 | ui_tools.root_list_all = {} 443 | 444 | for itemname, citem in pairs(cache.citems) do 445 | local entry = { 446 | citem = citem, 447 | itemdef = minetest.registered_items[itemname], 448 | 449 | -- buttons_grid related 450 | item = itemname, 451 | is_button = true 452 | } 453 | 454 | entry.sort_value = unifieddyes_sort_order(entry) or armor_sort_order(entry) or itemname 455 | citem.ui_item = entry 456 | if citem.cgroups["shape"] then 457 | table.insert(ui_tools.root_list_shape, entry) 458 | else 459 | table.insert(ui_tools.root_list, entry) 460 | end 461 | table.insert(ui_tools.root_list_all, entry) 462 | end 463 | end 464 | cache.register_on_cache_filled(prepare_root_lists) 465 | 466 | ----------------------------------------------------- 467 | -- Take a visual feedback on pressing button since the minetest client does nothing visible on pressing button 468 | ----------------------------------------------------- 469 | function ui_tools.image_button_feedback(playername, page, element) 470 | local function reset_background(playername, page, element) 471 | local state = smart_inventory.get_page_state(page, playername) 472 | if state then 473 | state:get(element):setBackground(nil) 474 | state.location.rootState:show() 475 | end 476 | end 477 | 478 | local state = smart_inventory.get_page_state(page, playername) 479 | if state then 480 | state:get(element):setBackground("halo.png") 481 | minetest.after(0.3, reset_background, playername, page, element) 482 | end 483 | 484 | end 485 | -------------------------------- 486 | return ui_tools 487 | -------------------------------------------------------------------------------- /workbench.lua: -------------------------------------------------------------------------------- 1 | local smartfs = smart_inventory.smartfs 2 | 3 | local function on_rightclick(pos, node, player, itemstack, pointed_thing) 4 | smartfs.get("smart_inventory:main"):show(player:get_player_name()) 5 | end 6 | 7 | local sound = nil 8 | if minetest.global_exists("default") then 9 | sound = default.node_sound_wood_defaults() 10 | end 11 | 12 | -- Return smart inventory workbench definition if enabled 13 | minetest.register_node("smart_inventory:workbench", { 14 | description = "Smart inventory workbench", 15 | groups = {cracky=2, choppy=2, oddly_breakable_by_hand=1}, 16 | sounds = sound, 17 | tiles = { 18 | "smart_inventory_workbench_top.png", 19 | "smart_inventory_workbench_top.png", 20 | "smart_inventory_workbench_sides.png", 21 | "smart_inventory_workbench_sides.png", 22 | "smart_inventory_workbench_front.png", 23 | "smart_inventory_workbench_front.png" 24 | }, 25 | on_rightclick = on_rightclick 26 | }) 27 | 28 | minetest.register_craft({ 29 | output = "smart_inventory:workbench", 30 | recipe = { 31 | {"default:coral_skeleton", "default:coral_skeleton"}, 32 | {"default:coral_skeleton", "default:coral_skeleton"} 33 | } 34 | }) 35 | --------------------------------------------------------------------------------