Compare commits

..

No commits in common. "main" and "1.4.1" have entirely different histories.
main ... 1.4.1

77 changed files with 1696 additions and 3178 deletions

View File

@ -1,24 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
- Mod version? Release or git?
- Engine version?
- LuaJIT enabled?
- Operating system?
- Did you try to disable other mods except i3?

View File

@ -18,4 +18,4 @@ jobs:
- name: Setup luacheck
run: luarocks install luacheck
- name: Run luacheck linter
run: cd util; lua luacheck.lua
run: luacheck .

View File

@ -1,7 +1,6 @@
allow_defined_top = true
ignore = {
"631", -- Line is too long.
"get_debug_grid",
}
@ -10,13 +9,10 @@ read_globals = {
"armor",
"skins",
"awards",
"hb",
"vector",
"string",
"table",
"ItemStack",
"VoxelArea",
"VoxelManip",
}
globals = {
@ -29,9 +25,7 @@ globals = {
exclude_files = {
"tests/test_compression.lua",
"tests/test_custom_recipes.lua",
"tests/test_operators.lua",
"tests/test_tabs.lua",
"tests/test_waypoints.lua",
".install",
".luarocks",

180
API.md
View File

@ -1,19 +1,6 @@
# API :screwdriver:
## API
### Table of Contents
1. [**Tabs**](https://github.com/minetest-mods/i3/blob/main/API.md#tabs)
2. [**Recipes**](https://github.com/minetest-mods/i3/blob/main/API.md#recipes)
3. [**Minitabs**](https://github.com/minetest-mods/i3/blob/main/API.md#minitabs)
4. [**Recipe filters**](https://github.com/minetest-mods/i3/blob/main/API.md#recipe-filters)
5. [**Search filters**](https://github.com/minetest-mods/i3/blob/main/API.md#search-filters)
6. [**Sorting methods**](https://github.com/minetest-mods/i3/blob/main/API.md#sorting-methods)
7. [**Item list compression**](https://github.com/minetest-mods/i3/blob/main/API.md#item-list-compression)
8. [**Waypoints**](https://github.com/minetest-mods/i3/blob/main/API.md#waypoints)
9. [**Miscellaneous**](https://github.com/minetest-mods/i3/blob/main/API.md#miscellaneous)
---
### Tabs
### Custom tabs
#### `i3.new_tab(name, def)`
@ -25,62 +12,51 @@ Custom tabs can be added to the `i3` inventory as follow (example):
```Lua
i3.new_tab("stuff", {
description = "Stuff",
image = "image.png", -- Optional, add an image next to the tab description
slots = true -- Optional, whether the inventory slots are shown or not. Disabled by default.
image = "image.png", -- Optional, adds an image next to the tab description
--
-- The functions below are all optional
--
-- Determine if the tab is visible by a player, return false to hide the tab
-- Determine if the tab is visible by a player, `false` or `nil` hide the tab
access = function(player, data)
local name = player:get_player_name()
return name == "singleplayer"
if name == "singleplayer" then
return false
end
end,
-- Build the formspec
formspec = function(player, data, fs)
fs("label", 3, 1, "Just a test")
fs"label[3,2;Lorem Ipsum]"
-- No need to return anything
fs("label[3,1;This is just a test]")
end,
-- Events handling happens here
fields = function(player, data, fields)
if fields.mybutton then
-- Do things
end
-- To prevent a formspec update, return false.
-- Otherwise: no need to return anything, it's automatic.
end,
})
```
- `player` is an `ObjectRef` to the user.
- `data` are the user data.
- `fs` is the formspec table which is callable with a metamethod. Every call adds a new entry.
- `fs` is the formspec table which is callable with a metamethod. Each call adds a new entry.
#### `i3.set_fs(player)`
Update the current formspec.
Updates the current formspec.
#### `i3.remove_tab(tabname)`
Delete a tab by name.
Deletes a tab by name.
#### `i3.get_current_tab(player)`
Return the current player tab. `player` is an `ObjectRef` to the user.
Returns the current player tab. `player` is an `ObjectRef` to the user.
#### `i3.set_tab(player[, tabname])`
Set the current tab by name. `player` is an `ObjectRef` to the user.
Sets the current tab by name. `player` is an `ObjectRef` to the user.
`tabname` can be omitted to get an empty tab.
#### `i3.override_tab(tabname, def)`
Override a tab by name. `def` is the tab definition like seen in `i3.set_tab`
Overrides a tab by name. `def` is the tab definition like seen in `i3.set_tab`.
#### `i3.tabs`
@ -88,17 +64,15 @@ A list of registered tabs.
---
### Recipes
### Custom recipes
Custom recipes are nonconventional crafts outside the main crafting grid.
They can be registered in-game dynamically and have a size beyond 3x3 items.
**Note:** the registration format differs from the default registration format in everything.
The width is automatically calculated depending where you place the commas.
The width is automatically calculated depending where you place the commas. Look at the examples attentively.
Examples:
#### Registering a custom crafting type
#### Registering a custom crafting type (example)
```Lua
i3.register_craft_type("digging", {
@ -107,7 +81,7 @@ i3.register_craft_type("digging", {
})
```
#### Registering a custom crafting recipe
#### Registering a custom crafting recipe (examples)
```Lua
i3.register_craft {
@ -146,7 +120,7 @@ i3.register_craft {
}
```
Multiple recipes can also be registered at once:
Multiples recipes can also be registered:
```Lua
i3.register_craft {
@ -180,53 +154,6 @@ i3.register_craft {
---
### Minitabs
Manage the tabs on the right panel of the inventory.
Allow to make a sensible list sorted by specific groups of items.
#### `i3.new_minitab(name, def)`
Add a new minitab (limited to 6).
- `name` is the tab name.
- `def` is the definition table.
Example:
```Lua
i3.new_minitab("test", {
description = "Test",
-- Whether this tab is visible or not. Optional.
access = function(player, data)
return player:get_player_name() == "singleplayer"
end,
-- Whether a specific item is shown in the list or not.
sorter = function(item, data)
return item:find"wood"
end
})
```
- `player` is an `ObjectRef` to the user.
- `data` are the user data.
- `item` is an item name string.
#### `i3.remove_minitab(name)`
Remove a minitab by name.
- `name` is the name of the tab to remove.
#### `i3.minitabs`
A list of registered minitabs.
---
### Recipe filters
Recipe filters can be used to filter the recipes shown to players. Progressive
@ -234,7 +161,7 @@ mode is implemented as a recipe filter.
#### `i3.add_recipe_filter(name, function(recipes, player))`
Add a recipe filter with the given `name`. The filter function returns the
Adds a recipe filter with the given `name`. The filter function returns the
recipes to be displayed, given the available recipes and an `ObjectRef` to the
user. Each recipe is a table of the form returned by
`minetest.get_craft_recipe`.
@ -256,7 +183,7 @@ end)
#### `i3.set_recipe_filter(name, function(recipe, player))`
Remove all recipe filters and add a new one.
Removes all recipe filters and adds a new one.
#### `i3.recipe_filters`
@ -282,7 +209,7 @@ Notes:
#### `i3.add_search_filter(name, function(item, values))`
Add a search filter.
Adds a search filter.
The search function must return a boolean value (whether the given item should be listed or not).
- `name` is the filter name.
@ -316,7 +243,7 @@ Sorting methods are used to filter the player's main inventory.
#### `i3.add_sorting_method(name, def)`
Add a player inventory sorting method.
Adds a player inventory sorting method.
- `name` is the method name.
- `def` is the method definition.
@ -351,9 +278,9 @@ A table containing all sorting methods.
#### `i3.compress(item, def)`
Add a new group of items to compress.
Adds a new group of items to compress.
- `item` is the item which represent the group of compressed items.
- `item` is the item that serve as stereotype for the group of compressed items.
- `def` is a table specifying the substring replace patterns to be used.
Example:
@ -372,62 +299,11 @@ A map of all compressed item groups, indexed by stereotypes.
---
### Waypoints
`i3` allows you to manage the waypoints of a specific player.
#### `i3.add_waypoint(player_name, def)`
Add a waypoint to specific player.
- `player_name` is the player name.
- `def` is the waypoint definition table.
Example:
```Lua
i3.add_waypoint("Test", {
player = "singleplayer",
pos = {x = 0, y = 2, z = 0},
color = 0xffff00,
-- image = "heart.png" (optional)
})
```
#### `i3.remove_waypoint(player_name, waypoint_name)`
Remove a waypoint for specific player.
- `player_name` is the player name.
- `waypoint_name` is the waypoint name.
Example:
```Lua
i3.remove_waypoint("singleplayer", "Test")
```
#### `i3.get_waypoints(player_name)`
Return a table of all waypoints of a specific player.
- `player_name` is the player name.
---
### Miscellaneous
#### `i3.hud_notif(name, msg[, img])`
Show a Steam-like HUD notification on the bottom-left corner of the screen.
- `name` is the player name.
- `msg` is the HUD message to show.
- `img` (optional) is the HUD image to show (preferably 16x16 px).
#### `i3.get_recipes(item)`
Return a table of recipes and usages of `item`.
Returns a table of recipes and usages of `item`.
#### `i3.export_url`
@ -441,4 +317,4 @@ given a number between 1 and 4.
---
**[1]** Add `i3` to the `secure.http_mods` or `secure.trusted_mods` setting in `minetest.conf`.
**¹** Add `i3` to the `secure.http_mods` or `secure.trusted_mods` setting in `minetest.conf`.

12
LICENSE
View File

@ -3,7 +3,7 @@ License of source code
The MIT License (MIT)
Copyright (c) 2020-2023 Jean-Patrick Guerrero and contributors.
Copyright (c) 2020-2021 Jean-Patrick Guerrero and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -23,16 +23,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Licenses of media (sounds)
--------------------------
Lone_Wolf (CC0):
i3_tab.ogg
i3_click.ogg
i3_cannot.ogg
MadPanCake (CC0):
i3_craft.ogg (https://freesound.org/people/MadPanCake/sounds/567849/)
Licenses of media (textures)
----------------------------

View File

@ -1,20 +1,19 @@
![logo](https://user-images.githubusercontent.com/7883281/145490041-d91d6bd6-a654-438d-b208-4d5736845ab7.png)
# i3
[![GitHub Release](https://img.shields.io/github/release/minetest-mods/i3.svg?style=flat)]() ![workflow](https://github.com/minetest-mods/i3/actions/workflows/luacheck.yml/badge.svg) [![ContentDB](https://content.minetest.net/packages/jp/i3/shields/downloads/)](https://content.minetest.net/packages/jp/i3/) [![PayPal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/jpg84240)
[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) ![workflow](https://github.com/minetest-mods/i3/actions/workflows/luacheck.yml/badge.svg) [![GitHub Release](https://img.shields.io/github/release/minetest-mods/i3.svg?style=flat)]() [![ContentDB](https://content.minetest.net/packages/jp/i3/shields/downloads/)](https://content.minetest.net/packages/jp/i3/) [![PayPal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/jpg84240)
#### **`i3`** is a next-generation inventory for Minetest.
This mod features a modern, powerful inventory menu with a good user experience.
**`i3`** provides a rich [**API**](https://github.com/minetest-mods/i3/blob/master/API.md) for mod developers who want to extend it.
This mod requires **Minetest 5.6+**
This mod requires **Minetest 5.4+**
#### List of features:
- Crafting Guide (survival mode only)
- Progressive Mode¹
- Quick Crafting
- 3D Player Model Real-Time Preview
- Isometric Map Preview
- Inventory Sorting (+ options: compression, reverse mode, automation, etc.)
- Item List Compression (**`moreblocks`** is supported)
- Item Bookmarks
@ -35,6 +34,7 @@ To enable it: `i3_progressive_mode = true` in `minetest.conf`.*
To use this mod in the best conditions:
- Use LuaJIT
- Use a HiDPI widescreen display
- Use the default Freetype font style
#### Troubleshooting
@ -42,8 +42,6 @@ To use this mod in the best conditions:
If the inventory's font size is too big on certain setups (namely Windows 10/11 or 144 DPI display), you should lower the
value of the setting `display_density_factor` in your `minetest.conf`. Note that the change is applied after restart.
You can also use the font size slider in the inventory, settings window.
#### Notes
`i3` uses a larger inventory than the usual inventories in Minetest games.
@ -52,6 +50,4 @@ The `i3` inventory is 9 slots wide by default, such as Minecraft.
Report bugs on the [**Bug Tracker**](https://github.com/minetest-mods/i3/issues).
**Video review on YouTube:** https://www.youtube.com/watch?v=Xd14BCdEZ3o
![Preview](https://user-images.githubusercontent.com/7883281/185755315-23c2fffa-203d-4115-9dc3-576c92615733.png)
![Preview](https://user-images.githubusercontent.com/7883281/142780121-564c1969-b95b-4cc9-99df-9f2362c67e07.png)

294
init.lua
View File

@ -1,46 +1,22 @@
print[[
Powered by
]]
local modpath = core.get_modpath"i3"
local http = core.request_http_api()
local storage = core.get_mod_storage()
local _loadfile = dofile(modpath .. "/src/preprocessor.lua")
local function lf(path)
return assert(_loadfile(modpath .. path))
return loadfile(modpath .. path)
end
i3 = {
version = 1161,
data = core.deserialize(storage:get_string"data") or {},
modules = {},
http = core.request_http_api(),
settings = {
debug_mode = false,
max_favs = 6,
max_waypoints = 30,
min_fs_version = 6,
item_btn_size = 1.1,
drop_bag_on_die = true,
wielditem_fade_after = 3,
save_interval = 600, -- Player data save interval (in seconds)
MAX_FAVS = 6,
INV_SIZE = 4*9,
HOTBAR_LEN = 9,
ITEM_BTN_SIZE = 1.1,
DROP_BAG_ON_DIE = true,
MIN_FORMSPEC_VERSION = 4,
SAVE_INTERVAL = 600, -- Player data save interval (in seconds)
hud_speed = 3,
hud_timer_max = 3,
damage_enabled = core.settings:get_bool"enable_damage",
progressive_mode = core.settings:get_bool"i3_progressive_mode",
},
categories = {
SUBCAT = {
"bag",
"armor",
"skins",
@ -48,46 +24,13 @@ i3 = {
"waypoints",
},
saves = { -- Metadata to save
META_SAVES = {
bag = true,
home = true,
sort = true,
collapse = true,
font_size = true,
hide_tabs = true,
waypoints = true,
inv_items = true,
auto_sorting = true,
inv_compress = true,
drop_items = true,
known_recipes = true,
wielditem_hud = true,
ignore_hotbar = true,
reverse_sorting = true,
legacy_inventory = true,
},
default_data = {
sort = 1,
font_size = 0,
collapse = true,
inv_compress = true,
},
files = {
api = lf"/src/api.lua",
bags = lf"/src/bags.lua",
caches = lf"/src/caches.lua",
callbacks = lf"/src/callbacks.lua",
common = lf"/src/common.lua",
compress = lf"/src/compression.lua",
detached = lf"/src/detached_inv.lua",
fields = lf"/src/fields.lua",
groups = lf"/src/groups.lua",
gui = lf"/src/gui.lua",
hud = lf"/src/hud.lua",
model_alias = lf"/src/model_aliases.lua",
progressive = lf"/src/progressive.lua",
styles = lf"/src/styles.lua",
},
-- Caches
@ -97,34 +40,211 @@ i3 = {
recipes_cache = {},
tabs = {},
cubes = {},
groups = {},
plants = {},
modules = {},
minitabs = {},
craft_types = {},
recipe_filters = {},
search_filters = {},
sorting_methods = {},
files = {
api = lf("/src/api.lua"),
bags = lf("/src/bags.lua"),
callbacks = lf("/src/callbacks.lua"),
common = lf("/src/common.lua"),
compress = lf("/src/compress.lua"),
detached = lf("/src/detached_inv.lua"),
groups = lf("/src/groups.lua"),
gui = lf("/src/gui.lua"),
model_alias = lf("/src/model_aliases.lua"),
progressive = lf("/src/progressive.lua"),
recipes = lf("/src/recipes.lua"),
styles = lf("/src/styles.lua"),
},
progressive_mode = core.settings:get_bool"i3_progressive_mode",
item_compression = core.settings:get_bool("i3_item_compression", true),
}
i3.files.common()
i3.files.api(http)
i3.files.api()
i3.files.compress()
i3.files.detached()
i3.files.fields()
i3.files.groups()
i3.files.callbacks(http, storage)
i3.files.callbacks()
if i3.settings.progressive_mode then
local storage = core.get_mod_storage()
local slz, dslz, copy = i3.get("slz", "dslz", "copy")
local str_to_pos, add_hud_waypoint = i3.get("str_to_pos", "add_hud_waypoint")
local set_fs = i3.set_fs
i3.data = dslz(storage:get_string"data") or {}
local init_bags = i3.files.bags()
local init_inventories = i3.files.detached()
local init_recipes = i3.files.recipes()
local function get_lang_code(info)
return info and info.lang_code
end
local function get_formspec_version(info)
return info and info.formspec_version or 1
end
local function outdated(name)
local fs = ("size[6.3,1.3]image[0,0;1,1;i3_book.png]label[1,0;%s]button_exit[2.6,0.8;1,1;;OK]"):format(
"Your Minetest client is outdated.\nGet the latest version on minetest.net to play the game.")
core.show_formspec(name, "i3_outdated", fs)
end
if rawget(_G, "armor") then
i3.modules.armor = true
armor:register_on_update(set_fs)
end
if rawget(_G, "skins") then
i3.modules.skins = true
end
if rawget(_G, "awards") then
i3.modules.awards = true
core.register_on_craft(function(_, player)
set_fs(player)
end)
core.register_on_dignode(function(_, _, player)
set_fs(player)
end)
core.register_on_placenode(function(_, _, player)
set_fs(player)
end)
core.register_on_chat_message(function(name)
local player = core.get_player_by_name(name)
set_fs(player)
end)
end
local function disable_inventories()
if rawget(_G, "sfinv") then
function sfinv.set_player_inventory_formspec() return end
sfinv.enabled = false
end
if rawget(_G, "unified_inventory") then
function unified_inventory.set_inventory_formspec() return end
end
end
local function init_data(player, info)
local name = player:get_player_name()
i3.data[name] = i3.data[name] or {}
local data = i3.data[name]
data.player_name = name
data.filter = ""
data.pagenum = 1
data.items = i3.init_items
data.items_raw = i3.init_items
data.favs = {}
data.sort = "alphabetical"
data.show_setting = "home"
data.ignore_hotbar = false
data.auto_sorting = false
data.reverse_sorting = false
data.inv_compress = true
data.export_counts = {}
data.tab = 1
data.itab = 1
data.subcat = 1
data.scrbar_inv = 0
data.lang_code = get_lang_code(info)
data.fs_version = info.formspec_version
local inv = player:get_inventory()
inv:set_size("main", i3.INV_SIZE)
core.after(0, set_fs, player)
end
local function init_waypoints(player)
local name = player:get_player_name()
local data = i3.data[name]
data.waypoints = data.waypoints or {}
for _, v in ipairs(data.waypoints) do
if not v.hide then
local id = add_hud_waypoint(player, v.name, str_to_pos(v.pos), v.color)
v.id = id
end
end
end
local function init_hudbar(player)
core.after(0, function()
player:hud_set_hotbar_itemcount(i3.HOTBAR_LEN)
player:hud_set_hotbar_image"i3_hotbar.png"
end)
end
local function save_data(player_name)
local _data = copy(i3.data)
for name, v in pairs(_data) do
for dat in pairs(v) do
if not i3.META_SAVES[dat] then
_data[name][dat] = nil
if player_name and i3.data[player_name] then
i3.data[player_name][dat] = nil -- To free up some memory
end
end
end
end
storage:set_string("data", slz(_data))
end
core.register_on_mods_loaded(function()
init_recipes()
disable_inventories()
end)
core.register_on_joinplayer(function(player)
local name = player:get_player_name()
local info = core.get_player_information and core.get_player_information(name)
if not info or get_formspec_version(info) < i3.MIN_FORMSPEC_VERSION then
return outdated(name)
end
init_data(player, info)
init_bags(player)
init_inventories(player)
init_waypoints(player)
init_hudbar(player)
end)
core.register_on_leaveplayer(function(player)
local name = player:get_player_name()
save_data(name)
end)
core.register_on_shutdown(save_data)
local function routine()
save_data()
core.after(i3.SAVE_INTERVAL, routine)
end
core.after(i3.SAVE_INTERVAL, routine)
if i3.progressive_mode then
i3.files.progressive()
end
if i3.settings.debug_mode then
lf("/tests/test_tabs.lua")()
lf("/tests/test_waypoints.lua")()
-- lf("/tests/test_operators.lua")()
lf("/tests/test_compression.lua")()
lf("/tests/test_custom_recipes.lua")()
end
--dofile(modpath .. "/tests/test_tabs.lua")
--dofile(modpath .. "/tests/test_compression.lua")
--dofile(modpath .. "/tests/test_custom_recipes.lua")

View File

@ -1,4 +1,3 @@
name = i3
description = Next-generation inventory
description = Tiling inventory for Minetest
optional_depends = 3d_armor, skinsdb, awards
min_minetest_version = 5.6

View File

@ -1,2 +1,5 @@
# The progressive mode shows recipes you can craft from items you ever had in your inventory.
i3_progressive_mode (Learn crafting recipes progressively) bool false
# Regroup the items of the same type in the item list.
i3_item_compression (Regroup items of the same type) bool true

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,11 @@
local http = ...
local make_fs, get_inventory_fs = i3.files.gui()
local make_fs = i3.files.gui()
IMPORT("sorter", "sort_inventory", "play_sound")
IMPORT("sort", "concat", "copy", "insert", "remove")
IMPORT("get_player_by_name", "add_hud_waypoint", "init_hud_notif")
IMPORT("gmatch", "split", "S", "err", "fmt", "reg_items", "pos_to_str")
IMPORT("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name")
local gmatch, split = i3.get("gmatch", "split")
local S, err, fmt, reg_items = i3.get("S", "err", "fmt", "reg_items")
local sorter, sort_inventory = i3.get("sorter", "sort_inventory")
local sort, concat, copy, insert, remove = i3.get("sort", "concat", "copy", "insert", "remove")
local true_str, true_table, is_str, is_func, is_table, clean_name =
i3.get("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name")
function i3.register_craft_type(name, def)
if not true_str(name) then
@ -22,8 +22,14 @@ end
function i3.register_craft(def)
local width, c = 0, 0
if http and true_str(def.url) then
http.fetch({url = def.url}, function(result)
if true_str(def.url) then
if not i3.http then
return err(fmt([[i3.register_craft(): Unable to reach %s.
No HTTP support for this mod: add it to the `secure.http_mods` or
`secure.trusted_mods` setting.]], def.url))
end
i3.http.fetch({url = def.url}, function(result)
if result.succeeded then
local t = core.parse_json(result.data)
if is_table(t) then
@ -51,7 +57,7 @@ function i3.register_craft(def)
def.result = nil
end
if not true_str(def.output) and not def.url then
if not true_str(def.output) then
return err "i3.register_craft: output missing"
end
@ -69,7 +75,9 @@ function i3.register_craft(def)
end
local cp = copy(def.grid)
sort(cp, function(a, b) return #a > #b end)
sort(cp, function(a, b)
return #a > #b
end)
width = #cp[1]
@ -80,33 +88,30 @@ function i3.register_craft(def)
end
for symbol in gmatch(concat(def.grid), ".") do
c++
c = c + 1
def.items[c] = def.key[symbol]
end
else
local items = copy(def.items)
local lines = {}
local items, len = def.items, #def.items
def.items = {}
for i = 1, #items do
lines[i] = split(items[i], ",", true)
for i = 1, len do
local rlen = #split(items[i], ",")
if #lines[i] > width then
width = #lines[i]
if rlen > width then
width = rlen
end
end
for i = 1, #items do
while #lines[i] < width do
insert(lines[i], items[i])
for i = 1, len do
while #split(items[i], ",") < width do
items[i] = fmt("%s,", items[i])
end
end
for _, line in ipairs(lines) do
for _, v in ipairs(line) do
c++
def.items[c] = clean_name(v)
end
for name in gmatch(concat(items, ","), "[%s%w_:]+") do
c = c + 1
def.items[c] = clean_name(name)
end
end
@ -150,11 +155,10 @@ function i3.add_search_filter(name, f)
end
function i3.get_recipes(item)
item = core.registered_aliases[item] or item
local recipes = i3.recipes_cache[item]
local usages = i3.usages_cache[item]
return {recipes = recipes, usages = usages}
return {
recipes = i3.recipes_cache[item],
usages = i3.usages_cache[item]
}
end
function i3.set_fs(player)
@ -167,13 +171,6 @@ function i3.set_fs(player)
sort_inventory(player, data)
end
for i, tab in ipairs(i3.tabs) do
if data.tab == i and tab.access and not tab.access(player, data) then
data.tab = 1
break
end
end
local fs = make_fs(player, data)
player:set_inventory_formspec(fs)
end
@ -193,21 +190,15 @@ function i3.new_tab(name, def)
insert(i3.tabs, def)
end
i3.new_tab("inventory", {
description = S"Inventory",
formspec = get_inventory_fs,
slots = true,
})
function i3.remove_tab(name)
if not true_str(name) then
return err "i3.remove_tab: tab name missing"
end
for i = #i3.tabs, 2, -1 do
local def = i3.tabs[i]
if def and name == def.name then
for i, def in ipairs(i3.tabs) do
if name == def.name then
remove(i3.tabs, i)
break
end
end
end
@ -228,8 +219,8 @@ function i3.set_tab(player, tabname)
return
end
for i, tab in ipairs(i3.tabs) do
if tab.name == tabname then
for i, def in ipairs(i3.tabs) do
if def.name == tabname then
data.tab = i
return
end
@ -307,45 +298,6 @@ function i3.compress(item, def)
end
end
function i3.hud_notif(name, msg, img)
if not true_str(name) then
return err "i3.hud_notif: player name missing"
elseif not true_str(msg) then
return err "i3.hud_notif: message missing"
end
local data = i3.data[name]
if not data then
return err "i3.hud_notif: no player data initialized"
end
local player = get_player_by_name(name)
if not player then return end
local max_y = -125
local def = {
show = true,
max = {x = -330, y = max_y},
hud_msg = msg,
hud_img = img and fmt("%s^[resize:64x64", img) or nil,
hud_timer = 0,
elems = init_hud_notif(player),
}
insert(data.hud.notifs, def)
play_sound(name, "i3_achievement", 1.0)
local nb_notifs = #data.hud.notifs
for i = 1, nb_notifs - 1 do
local notif = data.hud.notifs[i]
if notif then
notif.show = true
notif.max.y = ((nb_notifs - i) + 1) * max_y
notif.hud_timer = 0.5 * (nb_notifs - i)
end
end
end
function i3.add_sorting_method(name, def)
if not true_str(name) then
return err "i3.add_sorting_method: name missing"
@ -362,7 +314,7 @@ end
i3.add_sorting_method("alphabetical", {
description = S"Sort items by name (A-Z)",
func = function(list, data)
sorter(list, data, 1)
sorter(list, data.reverse_sorting, 1)
return list
end
})
@ -370,130 +322,7 @@ i3.add_sorting_method("alphabetical", {
i3.add_sorting_method("numerical", {
description = S"Sort items by number of items per stack",
func = function(list, data)
sorter(list, data, 2)
sorter(list, data.reverse_sorting, 2)
return list
end,
})
function i3.add_waypoint(name, def)
if not true_str(name) then
return err "i3.add_waypoint: name missing"
elseif not true_table(def) then
return err "i3.add_waypoint: definition missing"
elseif not true_str(def.player) then
return err "i3.add_waypoint: player name missing"
end
local data = i3.data[def.player]
if not data then
return err "i3.add_waypoint: no player data initialized"
end
local player = get_player_by_name(def.player)
local id = player and add_hud_waypoint(player, name, def.pos, def.color, def.image) or nil
insert(data.waypoints, {
name = name,
pos = pos_to_str(def.pos, 1),
color = def.color,
image = def.image,
id = id,
})
if data.subcat == 5 then
data.scrbar_inv += 1000
end
i3.set_fs(player)
end
function i3.remove_waypoint(player_name, name)
if not true_str(player_name) then
return err "i3.remove_waypoint: player name missing"
elseif not true_str(name) then
return err "i3.remove_waypoint: waypoint name missing"
end
local data = i3.data[player_name]
if not data then
return err "i3.remove_waypoint: no player data initialized"
end
local player = get_player_by_name(player_name)
for i = #data.waypoints, 1, -1 do
local waypoint = data.waypoints[i]
if waypoint and name == waypoint.name then
if player then
player:hud_remove(waypoint.id)
end
remove(data.waypoints, i)
end
end
i3.set_fs(player)
end
function i3.get_waypoints(player_name)
if not true_str(player_name) then
return err "i3.get_waypoints: player name missing"
end
local data = i3.data[player_name]
if not data then
return err "i3.get_waypoints: no player data initialized"
end
return data.waypoints
end
function i3.new_minitab(name, def)
if #i3.minitabs == 6 then
return err "i3.new_minitab: limit reached (6)"
elseif not true_str(name) then
return err "i3.new_minitab: name missing"
elseif not true_table(def) then
return err "i3.new_minitab: definition missing"
end
def.name = name
insert(i3.minitabs, def)
end
function i3.remove_minitab(name)
if not true_str(name) then
return err "i3.remove_minitab: name missing"
end
for i = #i3.minitabs, 2, -1 do
local v = i3.minitabs[i]
if v and v.name == name then
remove(i3.minitabs, i)
end
end
end
i3.new_minitab("all", {
description = "All",
sorter = function()
return true
end
})
i3.new_minitab("nodes", {
description = "Nodes",
sorter = function(item)
return core.registered_nodes[item]
end
})
i3.new_minitab("items", {
description = "Items",
sorter = function(item)
return core.registered_craftitems[item] or core.registered_tools[item]
end
})

View File

@ -1,8 +1,8 @@
local set_fs = i3.set_fs
IMPORT("get_bag_description", "ItemStack")
IMPORT("S", "ES", "fmt", "msg", "slz", "dslz")
IMPORT("get_group", "play_sound", "get_detached_inv", "create_inventory")
local ItemStack = ItemStack
local S, ES, fmt, msg, slz, dslz = i3.get("S", "ES", "fmt", "msg", "slz", "dslz")
local get_group, play_sound, get_detached_inv, create_inventory =
i3.get("get_group", "play_sound", "get_detached_inv", "create_inventory")
local function get_content(content)
local t = {}
@ -52,8 +52,6 @@ local function init_bags(player)
on_take = function()
data.bag = nil
data.bag_rename = nil
local content = get_detached_inv("bag_content", name)
content:set_list("main", {})
@ -70,10 +68,9 @@ local function init_bags(player)
local function save_content(inv)
local bagstack = bag:get_stack("main", 1)
local meta = bagstack:get_meta()
local desc = get_bag_description(data, bagstack)
meta:set_string("description", "")
if inv:is_empty"main" then
meta:set_string("description", desc)
meta:set_string("content", "")
else
local list = inv:get_list"main"
@ -83,7 +80,7 @@ local function init_bags(player)
local stack = list[i]
if not stack:is_empty() then
c++
c = c + 1
t[i] = stack:to_string()
end
end
@ -91,7 +88,7 @@ local function init_bags(player)
local bag_size = get_group(bagstack:get_name(), "bag")
local percent = fmt("%d", (c * 100) / (bag_size * 4))
meta:set_string("description", ES("@1 (@2% full)", desc, percent))
meta:set_string("description", ES("@1 (@2% full)", bagstack:get_description(), percent))
meta:set_string("content", slz(t))
end
@ -105,18 +102,6 @@ local function init_bags(player)
on_move = save_content,
on_put = save_content,
on_take = save_content,
allow_put = function(_, _, _, stack)
local meta = stack:get_meta()
local content = dslz(meta:get_string"content")
if content then
msg(name, "You cannot put a bag in another bag")
return 0, play_sound(name, "i3_cannot", 0.8)
end
return stack:get_count()
end,
}, name)
bag_content:set_size("main", 4*4)

View File

@ -1,11 +1,439 @@
local http, storage = ...
local init_bags = i3.files.bags()
local fill_caches = i3.files.caches(http)
local init_hud = i3.files.hud()
local set_fs = i3.set_fs
local _, get_inventory_fs = i3.files.gui()
IMPORT("slz", "min", "insert", "copy", "ItemStack")
IMPORT("spawn_item", "reset_data", "get_detached_inv", "play_sound", "update_inv_size")
local set_fs = i3.set_fs
local ItemStack = ItemStack
local S, clr = i3.get("S", "clr")
local min, random = i3.get("min", "random")
local reg_items, reg_aliases = i3.get("reg_items", "reg_aliases")
local fmt, find, match, sub, lower, split = i3.get("fmt", "find", "match", "sub", "lower", "split")
local vec_new, vec_eq, vec_round = i3.get("vec_new", "vec_eq", "vec_round")
local sort, copy, insert, remove, indexof = i3.get("sort", "copy", "insert", "remove", "indexof")
local msg, is_fav, pos_to_str, str_to_pos, add_hud_waypoint, play_sound, spawn_item =
i3.get("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound", "spawn_item")
local search, get_sorting_idx, sort_inventory, sort_by_category, get_recipes, get_detached_inv =
i3.get("search", "get_sorting_idx", "sort_inventory", "sort_by_category", "get_recipes", "get_detached_inv")
local valid_item, get_stack, craft_stack, clean_name, compressible, check_privs, safe_teleport =
i3.get("valid_item", "get_stack", "craft_stack", "clean_name", "compressible", "check_privs", "safe_teleport")
local function reset_data(data)
data.filter = ""
data.expand = ""
data.pagenum = 1
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.query_item = nil
data.recipes = nil
data.usages = nil
data.export_rcp = nil
data.export_usg = nil
data.alt_items = nil
data.confirm_trash = nil
data.show_settings = nil
data.show_setting = "home"
data.items = data.items_raw
if data.itab > 1 then
sort_by_category(data)
end
end
i3.new_tab("inventory", {
description = S"Inventory",
formspec = get_inventory_fs,
fields = function(player, data, fields)
local name = data.player_name
local inv = player:get_inventory()
local sb_inv = fields.scrbar_inv
if fields.skins then
local id = tonumber(fields.skins)
local _skins = skins.get_skinlist_for_player(name)
skins.set_player_skin(player, _skins[id])
end
if fields.drop_items then
local items = split(fields.drop_items, ",")
data.drop_items = items
end
for field in pairs(fields) do
if sub(field, 1, 4) == "btn_" then
data.subcat = indexof(i3.SUBCAT, sub(field, 5))
break
elseif sub(field, 1, 3) == "cb_" then
local str = sub(field, 4)
data[str] = false
if fields[field] == "true" then
data[str] = true
end
elseif sub(field, 1, 8) == "setting_" then
data.show_setting = match(field, "_(%w+)$")
elseif find(field, "waypoint_%d+") then
local id, action = match(field, "_(%d+)_(%w+)$")
id = tonumber(id)
local waypoint = data.waypoints[id]
if not waypoint then return end
if action == "delete" then
player:hud_remove(waypoint.id)
remove(data.waypoints, id)
elseif action == "teleport" then
local pos = vec_new(str_to_pos(waypoint.pos))
safe_teleport(player, pos)
msg(name, fmt("Teleported to %s", clr("#ff0", waypoint.name)))
elseif action == "refresh" then
local color = random(0xffffff)
waypoint.color = color
player:hud_change(waypoint.id, "number", color)
elseif action == "hide" then
if waypoint.hide then
local new_id = add_hud_waypoint(
player, waypoint.name, str_to_pos(waypoint.pos), waypoint.color)
waypoint.id = new_id
waypoint.hide = nil
else
player:hud_remove(waypoint.id)
waypoint.hide = true
end
end
break
end
end
if fields.quit then
data.confirm_trash = nil
data.show_settings = nil
elseif fields.trash then
data.show_settings = nil
data.confirm_trash = true
elseif fields.settings then
if not data.show_settings then
data.confirm_trash = nil
data.show_settings = true
else
data.show_settings = nil
end
elseif fields.confirm_trash_yes or fields.confirm_trash_no then
if fields.confirm_trash_yes then
inv:set_list("main", {})
inv:set_list("craft", {})
end
data.confirm_trash = nil
elseif fields.close_settings then
data.show_settings = nil
elseif fields.sort then
sort_inventory(player, data)
elseif fields.prev_sort or fields.next_sort then
local idx = get_sorting_idx(data.sort)
local tot = #i3.sorting_methods
idx = idx - (fields.prev_sort and 1 or -1)
if idx > tot then
idx = 1
elseif idx == 0 then
idx = tot
end
data.sort = i3.sorting_methods[idx].name
elseif fields.home then
if not data.home then
return msg(name, "No home set")
elseif not check_privs(name, {home = true}) then
return msg(name, "'home' privilege missing")
end
safe_teleport(player, str_to_pos(data.home))
msg(name, S"Welcome back home!")
elseif fields.set_home then
data.home = pos_to_str(player:get_pos(), 1)
elseif sb_inv and sub(sb_inv, 1, 3) == "CHG" then
data.scrbar_inv = tonumber(match(sb_inv, "%d+"))
return
elseif fields.waypoint_add then
local pos = player:get_pos()
for _, v in ipairs(data.waypoints) do
if vec_eq(vec_round(pos), vec_round(str_to_pos(v.pos))) then
play_sound(name, "i3_cannot", 0.8)
return msg(name, "You already set a waypoint at this position")
end
end
local waypoint = fields.waypoint_name
if fields.waypoint_name == "" then
waypoint = "Waypoint"
end
local color = random(0xffffff)
local id = add_hud_waypoint(player, waypoint, pos, color)
insert(data.waypoints, {
name = waypoint,
pos = pos_to_str(pos, 1),
color = color,
id = id,
})
data.scrbar_inv = data.scrbar_inv + 1000
end
return set_fs(player)
end,
})
local function select_item(player, data, _f)
local item
for field in pairs(_f) do
if find(field, ":") then
item = field
break
end
end
if not item then return end
if compressible(item, data) then
local idx
for i = 1, #data.items do
local it = data.items[i]
if it == item then
idx = i
break
end
end
if data.expand ~= "" then
data.alt_items = nil
if item == data.expand then
data.expand = nil
return
end
end
if idx and item ~= data.expand then
data.alt_items = copy(data.items)
data.expand = item
if i3.compress_groups[item] then
local items = copy(i3.compress_groups[item])
insert(items, fmt("_%s", item))
sort(items, function(a, b)
if a:sub(1, 1) == "_" then
a = a:sub(2)
end
return a < b
end)
local i = 1
for _, v in ipairs(items) do
if valid_item(reg_items[clean_name(v)]) then
insert(data.alt_items, idx + i, v)
i = i + 1
end
end
end
end
else
if sub(item, 1, 1) == "_" then
item = sub(item, 2)
elseif sub(item, 1, 6) == "group|" then
item = match(item, "([%w:_]+)$")
end
item = reg_aliases[item] or item
if not reg_items[item] then return end
if core.is_creative_enabled(data.player_name) then
local stack = ItemStack(item)
local stackmax = stack:get_stack_max()
stack = fmt("%s %s", item, stackmax)
return get_stack(player, stack)
end
if item == data.query_item then return end
local recipes, usages = get_recipes(player, item)
data.query_item = item
data.recipes = recipes
data.usages = usages
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.export_rcp = nil
data.export_usg = nil
end
end
local function rcp_fields(player, data, fields)
local sb_rcp, sb_usg = fields.scrbar_rcp, fields.scrbar_usg
if fields.cancel then
reset_data(data)
elseif fields.exit then
data.query_item = nil
elseif fields.key_enter_field == "filter" or fields.search then
if fields.filter == "" then
reset_data(data)
return set_fs(player)
end
local str = lower(fields.filter)
if data.filter == str then return end
data.filter = str
data.pagenum = 1
search(data)
if data.itab > 1 then
sort_by_category(data)
end
elseif fields.prev_page or fields.next_page then
if data.pagemax == 1 then return end
data.pagenum = data.pagenum - (fields.prev_page and 1 or -1)
if data.pagenum > data.pagemax then
data.pagenum = 1
elseif data.pagenum == 0 then
data.pagenum = data.pagemax
end
elseif fields.prev_recipe or fields.next_recipe then
local num = data.rnum + (fields.prev_recipe and -1 or 1)
data.rnum = data.recipes[num] and num or (fields.prev_recipe and #data.recipes or 1)
data.export_rcp = nil
data.scrbar_rcp = 1
elseif fields.prev_usage or fields.next_usage then
local num = data.unum + (fields.prev_usage and -1 or 1)
data.unum = data.usages[num] and num or (fields.prev_usage and #data.usages or 1)
data.export_usg = nil
data.scrbar_usg = 1
elseif fields.fav then
local fav, i = is_fav(data.favs, data.query_item)
local total = #data.favs
if total < i3.MAX_FAVS and not fav then
data.favs[total + 1] = data.query_item
elseif fav then
remove(data.favs, i)
end
elseif fields.export_rcp or fields.export_usg then
if fields.export_rcp then
data.export_rcp = not data.export_rcp
if not data.export_rcp then
data.scrbar_rcp = 1
end
else
data.export_usg = not data.export_usg
if not data.export_usg then
data.scrbar_usg = 1
end
end
elseif (sb_rcp and sub(sb_rcp, 1, 3) == "CHG") or (sb_usg and sub(sb_usg, 1, 3) == "CHG") then
data.scrbar_rcp = sb_rcp and tonumber(match(sb_rcp, "%d+"))
data.scrbar_usg = sb_usg and tonumber(match(sb_usg, "%d+"))
elseif fields.craft_rcp or fields.craft_usg then
craft_stack(player, data, fields.craft_rcp)
if fields.craft_rcp then
data.export_rcp = nil
data.scrbar_rcp = 1
else
data.export_usg = nil
data.scrbar_usg = 1
end
else
select_item(player, data, fields)
end
end
core.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name()
if formname == "i3_outdated" then
return false, core.kick_player(name, S"Come back when your client is up-to-date.")
elseif formname ~= "" then
return false
end
-- No-op buttons
if fields.player_name or fields.awards or fields.home_pos or fields.pagenum or
fields.no_item or fields.no_rcp or fields.select_sorting or fields.sort_method or
fields.bg_content then
return false
end
--print(dump(fields))
local data = i3.data[name]
if not data then return end
for f in pairs(fields) do
if sub(f, 1, 4) == "tab_" then
local tabname = sub(f, 5)
i3.set_tab(player, tabname)
break
elseif sub(f, 1, 5) == "itab_" then
data.pagenum = 1
data.itab = tonumber(f:sub(-1))
sort_by_category(data)
break
end
end
rcp_fields(player, data, fields)
local tab = i3.tabs[data.tab]
if tab and tab.fields then
return true, tab.fields(player, data, fields)
end
return true, set_fs(player)
end)
core.register_on_player_hpchange(function(player, hpchange)
local name = player:get_player_name()
@ -23,7 +451,7 @@ core.register_on_dieplayer(function(player)
local data = i3.data[name]
if not data then return end
if i3.settings.drop_bag_on_die then
if i3.DROP_BAG_ON_DIE then
local bagstack = ItemStack(data.bag)
spawn_item(player, bagstack)
end
@ -64,216 +492,3 @@ core.register_on_player_inventory_action(function(player, _, _, info)
set_fs(player)
end
end)
if core.global_exists"armor" then
i3.modules.armor = true
local group_indexes = {
{"armor_head", "i3_heavy_helmet"},
{"armor_torso", "i3_heavy_armor"},
{"armor_legs", "i3_heavy_leggings"},
{"armor_feet", "i3_heavy_boots"},
{"armor_shield", "i3_heavy_shield"},
}
local function check_group(def, group)
return def.groups[group] and def.groups[group] > 0
end
armor:register_on_equip(function(player, idx, stack)
local _, armor_inv = armor:get_valid_player(player, "3d_armor")
local def = stack:get_definition()
local name = player:get_player_name()
local data = i3.data[name]
for i, v in ipairs(group_indexes) do
local group, sound = unpack(v)
local stackname = stack:get_name()
if stackname:find"wood" or stackname:find"stone" or stackname:find"cactus" then
sound = sound:gsub("heavy", "light")
end
if i == idx and check_group(def, group) then
data.armor_allow = sound
return armor:register_on_update(set_fs)
end
end
data.armor_disallow = true
armor_inv:remove_item("armor", stack)
end)
armor:register_on_update(function(player)
local _, armor_inv = armor:get_valid_player(player, "3d_armor")
if not armor_inv then return end
for i = 1, 5 do
local stack = armor_inv:get_stack("armor", i)
local def = stack:get_definition()
for j, v in ipairs(group_indexes) do
local group = v[1]
if check_group(def, group) and i ~= j then
armor_inv:set_stack("armor", i, armor_inv:get_stack("armor", j))
armor_inv:set_stack("armor", j, stack)
return play_sound(player:get_player_name(), "i3_cannot", 0.8)
end
end
end
end)
core.register_on_player_inventory_action(function(player, action, _, info)
if action ~= "take" then return end
local name = player:get_player_name()
local data = i3.data[name]
if data.armor_disallow then
local inv = player:get_inventory()
inv:set_stack("main", info.index, info.stack)
data.armor_disallow = nil
play_sound(name, "i3_cannot", 0.8)
elseif data.armor_allow then
play_sound(name, data.armor_allow, 0.8)
data.armor_allow = nil
end
end)
end
if core.global_exists"skins" then
i3.modules.skins = true
end
if core.global_exists"awards" then
i3.modules.awards = true
core.register_on_craft(function(_, player)
set_fs(player)
end)
core.register_on_dignode(function(_, _, player)
set_fs(player)
end)
core.register_on_placenode(function(_, _, player)
set_fs(player)
end)
core.register_on_chat_message(function(name)
local player = core.get_player_by_name(name)
set_fs(player)
end)
end
local function disable_inventories()
if rawget(_G, "sfinv") then
function sfinv.set_player_inventory_formspec() return end
sfinv.enabled = false
end
if rawget(_G, "unified_inventory") then
function unified_inventory.set_inventory_formspec() return end
end
end
core.register_on_mods_loaded(function()
fill_caches()
disable_inventories()
end)
local function get_lang_code(info)
return info and info.lang_code
end
local function get_formspec_version(info)
return info and info.formspec_version or 1
end
local function outdated(name)
core.show_formspec(name, "i3_outdated",
("size[6.5,1.3]image[0,0;1,1;i3_book.png]label[1,0;%s]button_exit[2.6,0.8;1,1;;OK]"):format(
"Your Minetest client is outdated.\nGet the latest version on minetest.net to play the game."))
end
local function init_data(player, info)
local name = player:get_player_name()
i3.data[name] = i3.data[name] or {}
local data = i3.data[name]
for k, v in pairs(i3.default_data) do
local val = data[k]
if val == nil then
val = v
end
data[k] = val
end
data.player_name = name
data.filter = ""
data.pagenum = 1
data.skin_pagenum = 1
data.items = i3.init_items
data.items_raw = i3.init_items
data.favs = {}
data.show_setting = "home"
data.crafting_counts = {}
data.tab = 1
data.itab = 1
data.subcat = 1
data.scrbar_inv = 0
data.lang_code = get_lang_code(info)
data.fs_version = info.formspec_version
update_inv_size(player, data)
core.after(0, set_fs, player)
end
local function save_data(player_name)
local _data = copy(i3.data)
for name, v in pairs(_data) do
for dat in pairs(v) do
if not i3.saves[dat] then
_data[name][dat] = nil
if player_name and i3.data[player_name] then
i3.data[player_name][dat] = nil -- To free up some memory
end
end
end
end
storage:set_string("data", slz(_data))
end
insert(core.registered_on_joinplayers, 1, function(player)
local name = player:get_player_name()
local info = core.get_player_information and core.get_player_information(name)
if not info or get_formspec_version(info) < i3.settings.min_fs_version then
return outdated(name)
end
init_data(player, info)
init_bags(player)
init_hud(player)
end)
core.register_on_leaveplayer(function(player)
local name = player:get_player_name()
save_data(name)
end)
core.register_on_shutdown(save_data)
local function routine()
save_data()
core.after(i3.settings.save_interval, routine)
end
core.after(i3.settings.save_interval, routine)

View File

@ -1,21 +1,9 @@
local vec = vector.new
local ItemStack = ItemStack
local loadstring = loadstring
local reg_items = core.registered_items
local translate = core.get_translated_string
local sort, concat, insert = table.sort, table.concat, table.insert
local min, floor, ceil = math.min, math.floor, math.ceil
local fmt, find, match, gmatch, sub, split, lower, upper =
string.format, string.find, string.match, string.gmatch,
string.sub, string.split, string.lower, string.upper
if not core.registered_privileges.creative then
core.register_privilege("creative", {
description = "Allow player to use creative inventory",
give_to_singleplayer = false,
give_to_admin = false,
})
end
local fmt, find, match, gmatch, sub, split, lower =
string.format, string.find, string.match, string.gmatch, string.sub, string.split, string.lower
local old_is_creative_enabled = core.is_creative_enabled
@ -27,7 +15,7 @@ function core.is_creative_enabled(name)
return core.check_player_privs(name, {creative = true}) or old_is_creative_enabled(name)
end
local S = core.get_translator"i3"
local S = core.get_translator "i3"
local ES = function(...) return core.formspec_escape(S(...)) end
local function is_num(x)
@ -73,28 +61,6 @@ local function round(num, decimal)
return floor(num * mul + 0.5) / mul
end
local function toupper(str)
return str:gsub("%f[%w]%l", upper):gsub("_", " ")
end
local function utf8_len(str)
local c = 0
for _ in str:gmatch"[%z\1-\127\194-\244][\128-\191]*" do -- Arguably working duct-tape code
c++
end
return c
end
local function get_bag_description(data, stack)
local desc = translate(data.lang_code, stack:get_description())
desc = split(desc, "(")[1] or desc
desc = toupper(desc:trim())
return desc
end
local function search(data)
reset_compression(data)
@ -118,8 +84,8 @@ local function search(data)
for i = 1, #data.items_raw do
local item = data.items_raw[i]
local def = reg_items[item]
local desc = lower(translate(data.lang_code, def.description)) or ""
local def = core.registered_items[item]
local desc = lower(core.get_translated_string(data.lang_code, def and def.description)) or ""
local search_in = fmt("%s %s", item, desc)
local temp, j, to_add = {}, 1
@ -135,7 +101,7 @@ local function search(data)
temp[item] = true
end
j++
j = j + 1
end
end
else
@ -154,7 +120,7 @@ local function search(data)
end
if to_add then
c++
c = c + 1
filtered_list[c] = item
end
end
@ -182,7 +148,7 @@ local function table_merge(t1, t2, hash)
local c = #t1
for i = 1, #t2 do
c++
c = c + 1
t1[c] = t2[i]
end
end
@ -208,7 +174,7 @@ local function array_diff(t1, t2)
for i = 1, #t1 do
local v = t1[i]
if hash[v] then
c++
c = c + 1
diff[c] = v
end
end
@ -216,22 +182,18 @@ local function array_diff(t1, t2)
return diff
end
local function table_eq(t1, t2)
local ty1, ty2 = type(t1), type(t2)
if ty1 ~= ty2 then return end
local function rcp_eq(rcp, rcp2)
if rcp.type ~= rcp2.type then return end
if rcp.width ~= rcp2.width then return end
if #rcp.items ~= #rcp2.items then return end
if rcp.output ~= rcp2.output then return end
if ty1 ~= "table" and ty2 ~= "table" then
return t1 == t2
for i, item in pairs(rcp.items) do
if item ~= rcp2.items[i] then return end
end
for k, v in pairs(t1) do
local v2 = t2[k]
if v2 == nil or not table_eq(v, v2) then return end
end
for k, v in pairs(t2) do
local v1 = t1[k]
if v1 == nil or not table_eq(v1, v) then return end
for i, item in pairs(rcp2.items) do
if item ~= rcp.items[i] then return end
end
return true
@ -250,7 +212,9 @@ local function is_group(item)
end
local function extract_groups(str)
return split(sub(str, 7), ",")
if sub(str, 1, 6) == "group:" then
return split(sub(str, 7), ",")
end
end
local function item_has_groups(item_groups, groups)
@ -267,56 +231,30 @@ local function valid_item(def)
def.description and def.description ~= ""
end
local function get_group_stereotype(group)
local stereotype = i3.group_stereotypes[group]
local def = reg_items[stereotype]
local function groups_to_items(groups, get_all)
if not get_all and #groups == 1 then
local group = groups[1]
local stereotype = i3.group_stereotypes[group]
local def = core.registered_items[stereotype]
if valid_item(def) then
return stereotype
end
end
local function groups_to_items(groups)
local names = {}
for name, def in pairs(reg_items) do
if valid_item(def) and item_has_groups(def.groups, groups) then
insert(names, name)
if valid_item(def) then
return stereotype
end
end
sort(names)
local names = {}
return names
end
local function is_cube(drawtype)
return drawtype == "normal" or drawtype == "liquid" or
sub(drawtype, 1, 9) == "glasslike" or
sub(drawtype, 1, 8) == "allfaces"
end
local function get_cube(tiles)
if not true_table(tiles) then
return "i3_blank.png"
for name, def in pairs(core.registered_items) do
if valid_item(def) and item_has_groups(def.groups, groups) then
if get_all then
insert(names, name)
else
return name
end
end
end
local top = tiles[1] or "i3_blank.png"
if is_table(top) then
top = top.name or top.image
end
local left = tiles[3] or top or "i3_blank.png"
if is_table(left) then
left = left.name or left.image
end
local right = tiles[5] or left or "i3_blank.png"
if is_table(right) then
right = right.name or right.image
end
return core.inventorycube(top, left, right)
return get_all and names or ""
end
local function apply_recipe_filters(recipes, player)
@ -327,24 +265,25 @@ local function apply_recipe_filters(recipes, player)
return recipes
end
local function recipe_filter_set()
return next(i3.recipe_filters)
end
local function compression_active(data)
return data.collapse and not recipe_filter_set() and data.filter == ""
return i3.item_compression and not next(i3.recipe_filters) and data.filter == ""
end
local function compressible(item, data)
return compression_active(data) and i3.compress_groups[item]
end
local function is_fav(data)
for i = 1, #data.favs do
if data.favs[i] == data.query_item then
return i
local function is_fav(favs, query_item)
local fav, i
for j = 1, #favs do
if favs[j] == query_item then
fav = true
i = j
break
end
end
return fav, i
end
local function sort_by_category(data)
@ -360,8 +299,13 @@ local function sort_by_category(data)
for i = 1, #items do
local item = items[i]
local tab = i3.minitabs[data.itab]
local to_add = tab.sorter(item, data)
local to_add = true
if data.itab == 2 then
to_add = core.registered_nodes[item]
elseif data.itab == 3 then
to_add = core.registered_craftitems[item] or core.registered_tools[item]
end
if to_add then
insert(new, item)
@ -375,15 +319,15 @@ local function spawn_item(player, stack)
local dir = player:get_look_dir()
local ppos = player:get_pos()
ppos.y = ppos.y + player:get_properties().eye_height
local look_at = ppos + dir
local look_at = vector.add(ppos, vector.multiply(dir, 1))
core.add_item(look_at, stack)
end
local function get_recipes(player, item)
item = core.registered_aliases[item] or item
local recipes = i3.recipes_cache[item]
local usages = i3.usages_cache[item]
local clean_item = core.registered_aliases[item] or item
local recipes = i3.recipes_cache[clean_item]
local usages = i3.usages_cache[clean_item]
if recipes then
recipes = apply_recipe_filters(recipes, player)
@ -392,6 +336,7 @@ local function get_recipes(player, item)
local no_recipes = not recipes or #recipes == 0
if no_recipes and not usages then return end
usages = apply_recipe_filters(usages, player)
local no_usages = not usages or #usages == 0
return not no_recipes and recipes or nil,
@ -408,61 +353,38 @@ local function get_stack(player, stack)
end
end
local function get_group_items(name)
local groups = extract_groups(name)
return i3.groups[name:sub(7)].items or groups_to_items(groups)
end
local function craft_stack(player, data, craft_rcp)
local inv = player:get_inventory()
local rcp_usg = craft_rcp and "recipe" or "usage"
local rcp_def = rcp_usg == "recipe" and data.recipes[data.rnum] or data.usages[data.unum]
local output = craft_rcp and data.recipes[data.rnum].output or data.usages[data.unum].output
output = ItemStack(output)
local stackname, stackcount, stackmax = output:get_name(), output:get_count(), output:get_stack_max()
local scrbar_val = data[fmt("scrbar_%s", craft_rcp and "rcp" or "usg")] or 1
for name, count in pairs(data.crafting_counts[rcp_usg].rcp) do
for name, count in pairs(data.export_counts[rcp_usg].rcp) do
local items = {[name] = count}
if is_group(name) then
items = {}
local item_groups = get_group_items(name)
local groups = extract_groups(name)
local item_groups = groups_to_items(groups, true)
local remaining = count
for _, item in ipairs(item_groups) do
for _name, _count in pairs(data.crafting_counts[rcp_usg].inv) do
if item == _name and remaining > 0 then
local c = min(remaining, _count)
items[item] = c
remaining -= c
end
if remaining == 0 then break end
for _name, _count in pairs(data.export_counts[rcp_usg].inv) do
if item == _name and remaining > 0 then
local c = min(remaining, _count)
items[item] = c
remaining = remaining - c
end
if remaining == 0 then break end
end
end
end
for item, v in pairs(items) do
for _ = 1, v * scrbar_val do
inv:remove_item("main", item)
for _, pair in ipairs(rcp_def.replacements or {}) do
local old_name, new_name = unpack(pair)
if is_group(old_name) then
local item_groups = get_group_items(old_name)
for _, it in ipairs(item_groups) do
if item == it then
get_stack(player, ItemStack(new_name))
end
end
elseif item == old_name then
get_stack(player, ItemStack(new_name))
end
end
end
for k, v in pairs(items) do
inv:remove_item("main", fmt("%s %s", k, v * scrbar_val))
end
end
@ -474,7 +396,7 @@ local function craft_stack(player, data, craft_rcp)
local c = min(stackmax, leftover)
local stack = ItemStack(fmt("%s %s", stackname, c))
get_stack(player, stack)
leftover -= stackmax
leftover = leftover - stackmax
end
end
@ -486,25 +408,33 @@ local function safe_teleport(player, pos)
local name = player:get_player_name()
play_sound(name, "i3_teleport", 0.8)
pos.y = pos.y + 0.5
local vel = player:get_velocity()
player:add_velocity(-vel)
local p = vec(pos)
p.y += 0.25
player:set_pos(p)
player:add_velocity(vector.multiply(vel, -1))
player:set_pos(pos)
end
local function sorter(inv, data, mode)
local function get_sorting_idx(name)
local idx = 1
for i, def in ipairs(i3.sorting_methods) do
if name == def.name then
idx = i
end
end
return idx
end
local function sorter(inv, reverse, mode)
sort(inv, function(a, b)
if mode == 1 then
a = translate(data.lang_code, a:get_short_description())
b = translate(data.lang_code, b:get_short_description())
a, b = a:get_name(), b:get_name()
else
a, b = a:get_count(), b:get_count()
end
if data.reverse_sorting then
if reverse then
return a > b
end
@ -551,7 +481,7 @@ local function compress_items(list, start_i)
insert(special, stack)
else
hash[name] = hash[name] or 0
hash[name] += count
hash[name] = hash[name] + count
end
end
end
@ -563,7 +493,7 @@ local function compress_items(list, start_i)
for _ = 1, iter do
insert(new_inv, ItemStack(fmt("%s %u", name, min(stackmax, leftover))))
leftover -= stackmax
leftover = leftover - stackmax
end
end
@ -571,15 +501,40 @@ local function compress_items(list, start_i)
return new_inv
end
local function drop_items(player, inv, list, start_i, rej)
for i = start_i, #list do
local stack = list[i]
local name = stack:get_name()
for _, it in ipairs(rej) do
if name == it then
spawn_item(player, stack)
inv:set_stack("main", i, ItemStack(""))
end
end
end
return inv:get_list"main"
end
local function sort_inventory(player, data)
local inv = player:get_inventory()
local list = inv:get_list"main"
local size = inv:get_size"main"
local start_i = data.ignore_hotbar and (data.hotbar_len + 1) or 1
local start_i = data.ignore_hotbar and 10 or 1
list = data.inv_compress and compress_items(list, start_i) or pre_sorting(list, start_i)
if true_table(data.drop_items) then
list = drop_items(player, inv, list, start_i, data.drop_items)
end
local new_inv = i3.sorting_methods[data.sort].func(list, data)
if data.inv_compress then
list = compress_items(list, start_i)
else
list = pre_sorting(list, start_i)
end
local idx = get_sorting_idx(data.sort)
local new_inv = i3.sorting_methods[idx].func(list, data)
if not new_inv then return end
if not data.ignore_hotbar then
@ -593,80 +548,17 @@ local function sort_inventory(player, data)
end
end
local function reset_data(data)
data.filter = ""
data.expand = ""
data.pagenum = 1
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.query_item = nil
data.enable_search = nil
data.goto_page = nil
data.recipes = nil
data.usages = nil
data.crafting_rcp = nil
data.crafting_usg = nil
data.alt_items = nil
data.confirm_trash = nil
data.show_settings = nil
data.show_setting = "home"
data.items = data.items_raw
if data.itab > 1 then
sort_by_category(data)
end
end
local function add_hud_waypoint(player, name, pos, color, image)
local function add_hud_waypoint(player, name, pos, color)
return player:hud_add {
hud_elem_type = image and "image_waypoint" or "waypoint",
hud_elem_type = "waypoint",
name = name,
text = image or "m",
scale = {x = 5, y = 5},
text = " m",
world_pos = pos,
number = color,
image = image,
z_index = -300,
}
end
local function init_hud_notif(player)
return {
bg = player:hud_add {
hud_elem_type = "image",
position = {x = 0, y = 1},
offset = {x = 10, y = 0},
alignment = {x = 1, y = 1},
scale = {x = 0.6, y = 0.6},
text = "i3_bg_notif.png",
z_index = 0xDEAD,
},
img = player:hud_add {
hud_elem_type = "image",
position = {x = 0, y = 1},
offset = {x = 20, y = 20},
alignment = {x = 1, y = 1},
scale = {x = 1, y = 1},
text = "",
z_index = 0xDEAD,
},
text = player:hud_add {
hud_elem_type = "text",
position = {x = 0, y = 1},
offset = {x = 100, y = 40},
alignment = {x = 1, y = 1},
number = 0xffffff,
text = "",
z_index = 0xDEAD,
style = 1,
}
}
end
local function get_detached_inv(name, player_name)
return core.get_inventory {
type = "detached",
@ -674,21 +566,6 @@ local function get_detached_inv(name, player_name)
}
end
local function update_inv_size(player, data)
data.hotbar_len = data.legacy_inventory and 8 or 9
data.inv_size = 4 * data.hotbar_len
local inv = player:get_inventory()
inv:set_size("main", data.inv_size)
player:hud_set_hotbar_itemcount(data.hotbar_len)
core.after(0, function()
player:hud_set_hotbar_image(data.legacy_inventory and "gui_hotbar.png" or "i3_hotbar.png")
end)
end
-- Much faster implementation of `unpack`
local function createunpack(n)
local ret = {"local t = ... return "}
@ -715,7 +592,6 @@ local _ = {
extract_groups = extract_groups,
item_has_groups = item_has_groups,
groups_to_items = groups_to_items,
get_group_stereotype = get_group_stereotype,
-- Compression
compressible = compressible,
@ -726,8 +602,8 @@ local _ = {
sorter = sorter,
get_recipes = get_recipes,
sort_inventory = sort_inventory,
get_sorting_idx = get_sorting_idx,
sort_by_category = sort_by_category,
recipe_filter_set = recipe_filter_set,
apply_recipe_filters = apply_recipe_filters,
-- Type checks
@ -743,16 +619,11 @@ local _ = {
msg = msg,
-- Misc. functions
is_cube = is_cube,
get_cube = get_cube,
ItemStack = ItemStack,
valid_item = valid_item,
spawn_item = spawn_item,
clean_name = clean_name,
play_sound = play_sound,
reset_data = reset_data,
safe_teleport = safe_teleport,
init_hud_notif = init_hud_notif,
add_hud_waypoint = add_hud_waypoint,
-- Core functions
@ -760,20 +631,15 @@ local _ = {
slz = core.serialize,
dslz = core.deserialize,
ESC = core.formspec_escape,
draw_cube = core.inventorycube,
get_group = core.get_item_group,
pos_to_str = core.pos_to_string,
str_to_pos = core.string_to_pos,
check_privs = core.check_player_privs,
get_player_by_name = core.get_player_by_name,
get_connected_players = core.get_connected_players,
-- Inventory
get_stack = get_stack,
craft_stack = craft_stack,
update_inv_size = update_inv_size,
get_detached_inv = get_detached_inv,
get_bag_description = get_bag_description,
create_inventory = core.create_detached_inventory,
-- Registered items
@ -798,8 +664,6 @@ local _ = {
split = string.split,
match = string.match,
gmatch = string.gmatch,
toupper = toupper,
utf8_len = utf8_len,
-- Table
maxn = table.maxn,
@ -813,12 +677,11 @@ local _ = {
is_table = is_table,
table_merge = table_merge,
table_replace = table_replace,
table_eq = table_eq,
rcp_eq = rcp_eq,
array_diff = array_diff,
-- Math
round = round,
abs = math.abs,
min = math.min,
max = math.max,
ceil = math.ceil,
@ -826,8 +689,10 @@ local _ = {
random = math.random,
-- Vectors
vec = vector.new,
vec_new = vector.new,
vec_add = vector.add,
vec_round = vector.round,
vec_eq = vector.equals,
}
function i3.get(...)

View File

@ -1,4 +1,4 @@
IMPORT("fmt", "copy", "insert")
local fmt, copy, insert = i3.get("fmt", "copy", "insert")
local wood_types = {
"acacia_wood", "aspen_wood", "junglewood", "pine_wood",
@ -277,7 +277,13 @@ for _, nodename in ipairs(v) do
t[nodename] = {}
for _, shape in ipairs(circular_saw_names) do
if shape[1] ~= "slope" or shape[2] ~= "" then
local to_add = true
if shape[1] == "slope" and shape[2] == "" then
to_add = nil
end
if to_add then
insert(t[nodename], fmt("%s_%s%s", shape[1], nodename, shape[2]))
end
end
@ -286,7 +292,7 @@ for _, nodename in ipairs(v) do
to_compress[fmt("%s:%s", mod, slope_name)] = {
replace = slope_name,
by = t[nodename],
by = t[nodename]
}
end
end

View File

@ -1,5 +1,5 @@
local set_fs = i3.set_fs
IMPORT("play_sound", "create_inventory")
local fmt, play_sound, create_inventory = i3.get("fmt", "play_sound", "create_inventory")
local trash = create_inventory("i3_trash", {
allow_put = function(_, _, _, stack)
@ -10,9 +10,6 @@ local trash = create_inventory("i3_trash", {
inv:set_list(listname, {})
local name = player:get_player_name()
local data = i3.data[name]
data.armor_allow = nil
play_sound(name, "i3_trash", 1.0)
if not core.is_creative_enabled(name) then
@ -22,3 +19,15 @@ local trash = create_inventory("i3_trash", {
})
trash:set_size("main", 1)
local function init_inventories(player)
local name = player:get_player_name()
local output_rcp = create_inventory(fmt("i3_output_rcp_%s", name), {}, name)
output_rcp:set_size("main", 1)
local output_usg = create_inventory(fmt("i3_output_usg_%s", name), {}, name)
output_usg:set_size("main", 1)
end
return init_inventories

View File

@ -1,476 +0,0 @@
local set_fs = i3.set_fs
IMPORT("min", "max", "vec_round")
IMPORT("reg_items", "reg_aliases")
IMPORT("sort", "copy", "insert", "remove", "indexof")
IMPORT("S", "random", "translate", "compressible", "ItemStack")
IMPORT("fmt", "find", "match", "sub", "lower", "split", "toupper")
IMPORT("valid_item", "get_stack", "craft_stack", "clean_name", "check_privs", "safe_teleport")
IMPORT("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound", "reset_data")
IMPORT("search", "sort_inventory", "sort_by_category", "get_recipes", "get_detached_inv", "update_inv_size")
local function inv_fields(player, data, fields)
local name = data.player_name
local inv = player:get_inventory()
if fields.dd_sorting_method then
data.sort = tonumber(fields.dd_sorting_method)
elseif fields.sb_font_size then
data.font_size = tonumber(fields.sb_font_size:match"-?%d+$")
end
for field in pairs(fields) do
if sub(field, 1, 4) == "btn_" then
data.subcat = indexof(i3.categories, sub(field, 5))
break
elseif sub(field, 1, 3) == "cb_" then
local str = sub(field, 4)
data[str] = false
if fields[field] == "true" then
data[str] = true
end
if str == "legacy_inventory" then
update_inv_size(player, data)
elseif str == "collapse" then
search(data)
end
elseif sub(field, 1, 8) == "setting_" then
data.show_setting = match(field, "_(%w+)$")
elseif sub(field, 1, 9) == "skin_btn_" then
local id = tonumber(field:match("%d+"))
local _skins = skins.get_skinlist_for_player(name)
play_sound(name, "i3_skin_change", 0.6)
skins.set_player_skin(player, _skins[id])
elseif find(field, "waypoint_%d+") then
local id, action = match(field, "_(%d+)_(%w+)$")
id = tonumber(id)
local waypoint = data.waypoints[id]
if not waypoint then return end
if action == "see" then
if data.waypoint_see and data.waypoint_see == id then
data.waypoint_see = nil
else
data.waypoint_see = id
end
elseif action == "delete" then
player:hud_remove(waypoint.id)
remove(data.waypoints, id)
elseif action == "teleport" then
local pos = str_to_pos(waypoint.pos)
safe_teleport(player, pos)
msg(name, S("Teleported to: @1", waypoint.name))
elseif action == "refresh" then
local color = random(0xffffff)
waypoint.color = color
player:hud_change(waypoint.id, "number", color)
elseif action == "hide" then
if waypoint.hide then
local new_id = add_hud_waypoint(
player, waypoint.name, str_to_pos(waypoint.pos), waypoint.color)
waypoint.id = new_id
waypoint.hide = nil
else
player:hud_remove(waypoint.id)
waypoint.hide = true
end
end
break
end
end
if fields.quit then
data.confirm_trash = nil
data.show_settings = nil
data.waypoint_see = nil
data.bag_rename = nil
data.goto_page = nil
if data.filter == "" then
data.enable_search = nil
end
elseif fields.trash then
data.show_settings = nil
data.confirm_trash = true
elseif fields.settings then
if not data.show_settings then
data.confirm_trash = nil
data.show_settings = true
else
data.show_settings = nil
end
elseif fields.confirm_trash_yes or fields.confirm_trash_no then
if fields.confirm_trash_yes then
inv:set_list("main", {})
inv:set_list("craft", {})
end
data.confirm_trash = nil
elseif fields.close_settings then
data.show_settings = nil
elseif fields.close_preview then
data.waypoint_see = nil
elseif fields.sort then
sort_inventory(player, data)
elseif fields.home then
if not data.home then
return msg(name, "No home set")
elseif not check_privs(name, {home = true}) then
return msg(name, "'home' privilege missing")
end
safe_teleport(player, str_to_pos(data.home))
msg(name, S"Welcome back home!")
elseif fields.set_home then
data.home = pos_to_str(player:get_pos(), 1)
elseif fields.bag_rename then
data.bag_rename = true
elseif fields.confirm_rename then
local bag = get_detached_inv("bag", name)
local bagstack = bag:get_stack("main", 1)
local meta = bagstack:get_meta()
local desc = translate(data.lang_code, bagstack:get_description())
local fill = split(desc, "(")[2]
local newname = fields.bag_newname:gsub("([%(%)])", "")
newname = toupper(newname:trim())
if fill then
newname = fmt("%s (%s", newname, fill)
end
meta:set_string("description", newname)
bag:set_stack("main", 1, bagstack)
data.bag = bagstack:to_string()
data.bag_rename = nil
elseif fields.waypoint_add then
local max_waypoints = i3.settings.max_waypoints
if #data.waypoints >= max_waypoints then
play_sound(name, "i3_cannot", 0.8)
return msg(name, fmt("Waypoints limit reached (%u)", max_waypoints))
end
local pos = player:get_pos()
for _, v in ipairs(data.waypoints) do
if vec_round(pos) == vec_round(str_to_pos(v.pos)) then
play_sound(name, "i3_cannot", 0.8)
return msg(name, S"You already have set a waypoint at this position")
end
end
local waypoint = fields.waypoint_name
if fields.waypoint_name == "" then
waypoint = "Waypoint"
end
local color = random(0xffffff)
local id = add_hud_waypoint(player, waypoint, pos, color)
insert(data.waypoints, {
name = waypoint,
pos = pos_to_str(pos, 1),
color = color,
id = id,
})
data.scrbar_inv += 1000
elseif fields.hide_debug_grid then
data.hide_debug_grid = not data.hide_debug_grid
end
end
local function select_item(player, data, fields)
local item
for field in pairs(fields) do
if find(field, ":") then
item = field
break
end
end
if not item then return end
if compressible(item, data) then
local idx
for i = 1, #data.items do
local it = data.items[i]
if it == item then
idx = i
break
end
end
if data.expand ~= "" then
data.alt_items = nil
if item == data.expand then
data.expand = nil
return
end
end
if idx and item ~= data.expand then
data.alt_items = copy(data.items)
data.expand = item
if i3.compress_groups[item] then
local items = copy(i3.compress_groups[item])
insert(items, fmt("_%s", item))
sort(items, function(a, b)
if a:sub(1, 1) == "_" then
a = a:sub(2)
end
return a < b
end)
local i = 1
for _, v in ipairs(items) do
if valid_item(reg_items[clean_name(v)]) then
insert(data.alt_items, idx + i, v)
i++
end
end
end
end
else
if sub(item, 1, 1) == "_" then
item = sub(item, 2)
elseif sub(item, 1, 6) == "group!" then
item = match(item, "([%w:_]+)$")
end
item = reg_aliases[item] or item
if not reg_items[item] then return end
if core.is_creative_enabled(data.player_name) then
local stack = ItemStack(item)
local stackmax = stack:get_stack_max()
stack = fmt("%s %s", item, stackmax)
return get_stack(player, stack)
end
if item == data.query_item then return end
local recipes, usages = get_recipes(player, item)
data.query_item = item
data.recipes = recipes
data.usages = usages
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.crafting_rcp = nil
data.crafting_usg = nil
end
end
local function rcp_fields(player, data, fields)
local sb_rcp, sb_usg = fields.scrbar_rcp, fields.scrbar_usg
if not data.hide_tabs and fields.filter and fields.filter == "" then
data.enable_search = nil
end
if fields.cancel then
reset_data(data)
elseif fields.exit then
data.query_item = nil
elseif fields.enable_search then
if data.hide_tabs then
data.enable_search = not data.enable_search
else
data.enable_search = true
end
elseif fields.filter and (fields.key_enter_field == "filter" or fields.search) then
if fields.filter == "" then
reset_data(data)
return set_fs(player)
end
local str = lower(fields.filter)
if data.filter == str then return end
data.filter = str
data.pagenum = 1
search(data)
if data.itab > 1 then
sort_by_category(data)
end
elseif fields.pagenum then
data.goto_page = not data.goto_page
elseif fields.goto_page then
local pagenum = tonumber(fields.goto_page)
data.pagenum = max(1, min(data.pagemax, pagenum or data.pagenum))
data.goto_page = nil
elseif fields.prev_page or fields.next_page then
if data.pagemax == 1 then return end
data.pagenum -= (fields.prev_page and 1 or -1)
if data.pagenum > data.pagemax then
data.pagenum = 1
elseif data.pagenum == 0 then
data.pagenum = data.pagemax
end
elseif fields.prev_skin or fields.next_skin then
if data.skin_pagemax == 1 then return end
data.skin_pagenum -= (fields.prev_skin and 1 or -1)
if data.skin_pagenum > data.skin_pagemax then
data.skin_pagenum = 1
elseif data.skin_pagenum == 0 then
data.skin_pagenum = data.skin_pagemax
end
elseif fields.prev_recipe or fields.next_recipe then
local num = data.rnum + (fields.prev_recipe and -1 or 1)
data.rnum = data.recipes[num] and num or (fields.prev_recipe and #data.recipes or 1)
data.crafting_rcp = nil
data.scrbar_rcp = 1
elseif fields.prev_usage or fields.next_usage then
local num = data.unum + (fields.prev_usage and -1 or 1)
data.unum = data.usages[num] and num or (fields.prev_usage and #data.usages or 1)
data.crafting_usg = nil
data.scrbar_usg = 1
elseif fields.fav then
local fav = is_fav(data)
if #data.favs < i3.settings.max_favs and not fav then
insert(data.favs, data.query_item)
elseif fav then
remove(data.favs, fav)
end
elseif fields.crafting_rcp or fields.crafting_usg then
if fields.crafting_rcp then
data.crafting_rcp = not data.crafting_rcp
if not data.crafting_rcp then
data.scrbar_rcp = 1
end
else
data.crafting_usg = not data.crafting_usg
if not data.crafting_usg then
data.scrbar_usg = 1
end
end
elseif (sb_rcp and sub(sb_rcp, 1, 3) == "CHG") or (sb_usg and sub(sb_usg, 1, 3) == "CHG") then
data.scrbar_rcp = sb_rcp and tonumber(match(sb_rcp, "%d+"))
data.scrbar_usg = sb_usg and tonumber(match(sb_usg, "%d+"))
elseif fields.craft_rcp or fields.craft_usg then
craft_stack(player, data, fields.craft_rcp)
if fields.craft_rcp then
data.crafting_rcp = nil
data.scrbar_rcp = 1
else
data.crafting_usg = nil
data.scrbar_usg = 1
end
else
select_item(player, data, fields)
end
end
core.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name()
if formname == "i3_outdated" then
return false, core.kick_player(name, S"Your Minetest client needs updating (www.minetest.net)")
elseif formname ~= "" then
return false
end
-- No-op buttons
if fields.player_name or fields.awards or fields.home_pos or fields.no_item or
fields.no_rcp or fields.select_sorting or fields.sort_method or fields.bg_content or
fields.quick_crafting then
return false
end
-- print(dump(fields))
local data = i3.data[name]
if not data then return end
local sb_inv = fields.scrbar_inv
if sb_inv and sub(sb_inv, 1, 3) == "CHG" then
data.scrbar_inv = tonumber(match(sb_inv, "%d+"))
return
end
for f in pairs(fields) do
if sub(f, 1, 4) == "tab_" then
local tabname = sub(f, 5)
i3.set_tab(player, tabname)
break
elseif sub(f, 1, 5) == "itab_" then
data.pagenum = 1
data.itab = tonumber(f:sub(-1))
sort_by_category(data)
break
end
end
rcp_fields(player, data, fields)
local tab = i3.tabs[data.tab]
if tab then
if tab.slots then
inv_fields(player, data, fields)
end
if tab.fields then
local ret = tab.fields(player, data, fields)
if ret == false then return end
end
end
return true, set_fs(player)
end)

View File

@ -1,4 +1,4 @@
IMPORT("S")
local S = i3.get("S")
i3.group_stereotypes = {
dye = "dye:white",
@ -11,7 +11,6 @@ i3.group_stereotypes = {
stone = "default:stone",
leaves = "default:leaves",
coal = "default:coal_lump",
fence = "default:fence_wood",
vessel = "vessels:glass_bottle",
flower = "flowers:dandelion_yellow",
water_bucket = "bucket:bucket_water",
@ -27,7 +26,6 @@ i3.group_names = {
glass = S"Any glass",
stick = S"Any stick",
stone = S"Any stone",
fence = S"Any fence",
carpet = S"Any carpet",
flower = S"Any flower",
leaves = S"Any leaves",

File diff suppressed because it is too large Load Diff

View File

@ -1,147 +0,0 @@
IMPORT("max", "ceil", "remove", "str_to_pos")
IMPORT("get_connected_players", "add_hud_waypoint")
local function init_hud(player)
local name = player:get_player_name()
local data = i3.data[name]
local wdesc_y = -90
if core.global_exists"hb" then
wdesc_y -= ceil(hb.hudbars_count / 2) * 5
elseif not i3.settings.damage_enabled then
wdesc_y += 15
end
data.hud = {
notifs = {},
wielditem = player:hud_add {
hud_elem_type = "text",
position = {x = 0.5, y = 1},
offset = {x = 0, y = wdesc_y},
alignment = {x = 0, y = -1},
number = 0xffffff,
text = "",
z_index = 0xDEAD,
style = 1,
}
}
end
local function get_progress(offset, max_val)
local progress = offset * (1 / (max_val - 5))
return 1 - (progress ^ 4)
end
local function show_hud(player, data, notif, idx, dt)
local hud_info_bg = player:hud_get(notif.elems.bg)
local offset = hud_info_bg.offset
if offset.y < notif.max.y then
notif.show = false
notif.hud_timer += dt
end
player:hud_change(notif.elems.text, "text", notif.hud_msg)
if notif.hud_img then
player:hud_change(notif.elems.img, "text", notif.hud_img)
end
if notif.show then
local speed = i3.settings.hud_speed * (100 * get_progress(offset.y, notif.max.y)) * dt
for _, def in pairs(notif.elems) do
local hud_info = player:hud_get(def)
player:hud_change(def, "offset", {
x = hud_info.offset.x,
y = hud_info.offset.y - (speed * max(1, (#data.hud.notifs - idx + 1) / 1.45))
})
end
elseif notif.show == false and notif.hud_timer >= i3.settings.hud_timer_max then
local speed = (i3.settings.hud_speed * 2.6) * (100 * get_progress(offset.x, notif.max.x)) * dt
for _, def in pairs(notif.elems) do
local hud_info = player:hud_get(def)
player:hud_change(def, "offset", {
x = hud_info.offset.x - speed,
y = hud_info.offset.y
})
if hud_info.offset.x < notif.max.x then
player:hud_remove(def)
remove(data.hud.notifs, idx)
end
end
end
end
core.register_globalstep(function(dt)
local players = get_connected_players()
players[0] = #players
for i = 1, players[0] do
local player = players[i]
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
for idx, notif in ipairs(data.hud.notifs) do
if notif.show ~= nil then
show_hud(player, data, notif, idx, dt)
end
end
local has_text = player:hud_get(data.hud.wielditem).text ~= ""
if not data.wielditem_hud then
if has_text then
player:hud_change(data.hud.wielditem, "text", "")
end
return
end
data.timer = (data.timer or 0) + dt
local wieldidx = player:get_wield_index()
if wieldidx == data.old_wieldidx then
if data.timer >= i3.settings.wielditem_fade_after and has_text then
player:hud_change(data.hud.wielditem, "text", "")
end
return
end
data.timer = 0
data.old_wieldidx = wieldidx
local wielditem = player:get_wielded_item()
local meta = wielditem:get_meta()
local meta_desc = meta:get_string"short_description"
meta_desc = meta_desc:gsub("\27", "")
meta_desc = core.strip_colors(meta_desc)
local desc = meta_desc ~= "" and meta_desc or wielditem:get_short_description()
player:hud_change(data.hud.wielditem, "text", desc:trim())
end
end)
local function init_waypoints(player)
local name = player:get_player_name()
local data = i3.data[name]
data.waypoints = data.waypoints or {}
for _, v in ipairs(data.waypoints) do
if not v.hide then
local id = add_hud_waypoint(player, v.name, str_to_pos(v.pos), v.color, v.image)
v.id = id
end
end
end
return function(player)
init_hud(player)
init_waypoints(player)
end

View File

@ -1,4 +1,4 @@
return {
local model_alias = {
["boats:boat"] = {name = "boats:boat", drawtype = "entity"},
["carts:cart"] = {name = "carts:cart", drawtype = "entity", frames = "0,0"},
["default:chest"] = {name = "default:chest_open"},
@ -9,3 +9,5 @@ return {
["doors:door_steel"] = {name = "doors:door_steel_a"},
["xpanes:door_steel_bar"] = {name = "xpanes:door_steel_bar_a"},
}
return model_alias

View File

@ -1,98 +0,0 @@
--[[ All source files have to be preprocessed before loading.
This allows implementing custom operators like bitwise ones. ]]
local fmt, split = string.format, string.split
local var = "[%w%.%[%]\"\'_]"
local modpath = core.get_modpath"i3"
local _,_, fs_elements = dofile(modpath .. "/src/styles.lua")
local operators = {
["([%+%-%*%^/&|])="] = function(a, b, c)
return fmt("%s = %s %s %s", a, a, b, c)
end,
["+%+"] = function(a, b)
return fmt("%s = %s + 1\n%s", a, a, b)
end,
["&"] = function(a, b)
return fmt("bit.band(%s, %s)", a, b)
end,
["|"] = function(a, b)
return fmt("bit.bor(%s, %s)", a, b)
end,
["<<"] = function(a, b)
return fmt("bit.lshift(%s, %s)", a, b)
end,
[">>"] = function(a, b)
return fmt("bit.rshift(%s, %s)", a, b)
end,
["<<="] = function(a, b)
return fmt("%s = bit.lshift(%s, %s)", a, a, b)
end,
[">>="] = function(a, b)
return fmt("%s = bit.rshift(%s, %s)", a, a, b)
end,
}
local function compile(data)
data = data:gsub("IMPORT%((.-)%)", function(a)
return "local " .. a:gsub("\"", "") .. " = i3.get(" .. a .. ")"
end)
data = data:gsub("([%w_]+)%(", function(a)
if fs_elements[a] then
return fmt("fs('%s',", a)
end
end)
data = data:gsub("([%w_]+)-%-\n", function(a)
return fmt("%s = %s - 1", a, a)
end)
for op, func in pairs(operators) do
data = data:gsub("(" .. var .. "+)%s?" .. op .. "%s?(" .. var .. "*)", func)
end
return data
end
local function _load(path, line, data, t)
if line then
if not t then
t = split(data, "\n")
end
t[line] = t[line]:gsub("(" .. var .. "+)%s?=%s?(" .. var .. "*)", "%2")
data = table.concat(t, "\n")
else
local file = assert(io.open(path, "r"))
data = file:read"*a"
file:close()
data = compile(data)
end
local l, err = loadstring(data)
if not l then
local err_line = tonumber(err:match(":(%d+):"))
if t then
print("err_line", err_line, t[err_line])
end
if line ~= err_line then
return _load(path, err_line, data, t)
end
end
return l, err
end
return function(path)
return _load(path) or loadfile(path)
end

View File

@ -1,23 +1,16 @@
local singleplayer = core.is_singleplayer()
local set_fs = i3.set_fs
local hud_notif = i3.hud_notif
local fmt, search, table_merge, array_diff =
i3.get("fmt", "search", "table_merge", "array_diff")
local is_group, extract_groups, item_has_groups, apply_recipe_filters =
i3.get("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters")
local POLL_FREQ = 0.25
IMPORT("reg_items", "reg_nodes", "fmt", "table_merge", "array_diff")
IMPORT("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters", "sort_by_category")
i3.remove_minitab"nodes"
i3.remove_minitab"items"
i3.new_minitab("unlocked", {
description = "Unlocked",
sorter = function(item, data)
return data.items_progress[item]
end
})
local HUD_TIMER_MAX = 1.5
local function get_filtered_items(player, data)
local items, known = {}, 0
local items, known, c = {}, 0, 0
for i = 1, #i3.init_items do
local item = i3.init_items[i]
@ -28,12 +21,18 @@ local function get_filtered_items(player, data)
usages = #apply_recipe_filters(usages or {}, player)
if recipes > 0 or usages > 0 then
items[item] = true
known += recipes + usages
c = c + 1
items[c] = item
if data then
known = known + recipes + usages
end
end
end
data.known_recipes = known
if data then
data.known_recipes = known
end
return items
end
@ -42,9 +41,7 @@ local function item_in_inv(item, inv_items)
local inv_items_size = #inv_items
if is_group(item) then
local groupname = item:sub(7)
local group_cache = i3.groups[groupname]
local groups = group_cache and group_cache.groups or extract_groups(item)
local groups = extract_groups(item)
for i = 1, inv_items_size do
local def = core.registered_items[inv_items[i]]
@ -89,7 +86,7 @@ local function progressive_filter(recipes, player)
for i = 1, #recipes do
local recipe = recipes[i]
if recipe_in_inv(recipe, data.inv_items) then
c++
c = c + 1
filtered[c] = recipe
end
end
@ -101,10 +98,6 @@ local item_lists = {"main", "craft", "craftpreview"}
local function get_inv_items(player)
local inv = player:get_inventory()
if not inv then
return {}
end
local stacks = {}
for i = 1, #item_lists do
@ -120,7 +113,7 @@ local function get_inv_items(player)
if not stack:is_empty() then
local name = stack:get_name()
if core.registered_items[name] then
c++
c = c + 1
inv_items[c] = name
end
end
@ -129,38 +122,129 @@ local function get_inv_items(player)
return inv_items
end
-- Workaround. Need an engine call to detect when the contents of
-- the player inventory changed, instead.
local function poll_new_items(player, data, join)
local inv_items = get_inv_items(player)
local diff = array_diff(inv_items, data.inv_items)
local function init_hud(player, data)
data.hud = {
bg = player:hud_add {
hud_elem_type = "image",
position = {x = 0.78, y = 1},
alignment = {x = 1, y = 1},
scale = {x = 370, y = 112},
text = "i3_bg.png",
z_index = 0xDEAD,
},
if join or #diff > 0 then
data.inv_items = table_merge(diff, data.inv_items)
local oldknown = data.known_recipes or 0
local items = get_filtered_items(player, data)
data.discovered = data.known_recipes - oldknown
book = player:hud_add {
hud_elem_type = "image",
position = {x = 0.79, y = 1.02},
alignment = {x = 1, y = 1},
scale = {x = 4, y = 4},
text = "i3_book.png",
z_index = 0xDEAD,
},
if data.discovered > 0 then
local msg = fmt("%u new recipe%s unlocked!", data.discovered, data.discovered > 1 and "s" or "")
local last_discovered = diff[1]
local img = reg_items[last_discovered].inventory_image
text = player:hud_add {
hud_elem_type = "text",
position = {x = 0.84, y = 1.04},
alignment = {x = 1, y = 1},
number = 0xffffff,
text = "",
z_index = 0xDEAD,
style = 1,
},
}
end
if reg_nodes[last_discovered] then
local id = core.get_content_id(last_discovered)
img = i3.cubes[id] or img
end
local function show_hud_success(player, data)
-- It'd better to have an engine function `hud_move` to only need
-- 2 calls for the notification's back and forth.
hud_notif(data.player_name, msg, img)
end
local hud_info_bg = player:hud_get(data.hud.bg)
local dt = 0.016
data.items_progress = items
sort_by_category(data)
set_fs(player)
if hud_info_bg.position.y <= 0.9 then
data.show_hud = false
data.hud_timer = (data.hud_timer or 0) + dt
end
core.after(POLL_FREQ, poll_new_items, player, data)
if data.show_hud then
for _, def in pairs(data.hud) do
local hud_info = player:hud_get(def)
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y - (dt / 5)
})
end
player:hud_change(data.hud.text, "text",
fmt("%u new recipe%s unlocked!", data.discovered, data.discovered > 1 and "s" or ""))
elseif data.show_hud == false then
if data.hud_timer >= HUD_TIMER_MAX then
for _, def in pairs(data.hud) do
local hud_info = player:hud_get(def)
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y + (dt / 5)
})
end
if hud_info_bg.position.y >= 1 then
data.show_hud = nil
data.hud_timer = nil
end
end
end
end
-- Workaround. Need an engine call to detect when the contents of
-- the player inventory changed, instead.
local function poll_new_items()
local players = core.get_connected_players()
for i = 1, #players do
local player = players[i]
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
local inv_items = get_inv_items(player)
local diff = array_diff(inv_items, data.inv_items)
if #diff > 0 then
data.inv_items = table_merge(diff, data.inv_items)
local oldknown = data.known_recipes or 0
local items = get_filtered_items(player, data)
data.discovered = data.known_recipes - oldknown
if data.show_hud == nil and data.discovered > 0 then
data.show_hud = true
end
data.items_raw = items
data.itab = 1
search(data)
set_fs(player)
end
end
core.after(POLL_FREQ, poll_new_items)
end
poll_new_items()
if singleplayer then
core.register_globalstep(function()
local name = "singleplayer"
local player = core.get_player_by_name(name)
local data = i3.data[name]
if data and data.show_hud ~= nil then
show_hud_success(player, data)
end
end)
end
i3.add_recipe_filter("Default progressive filter", progressive_filter)
@ -172,7 +256,19 @@ core.register_on_joinplayer(function(player)
data.inv_items = data.inv_items or {}
data.known_recipes = data.known_recipes or 0
data.discovered = data.discovered or 0
poll_new_items(player, data, true)
local oldknown = data.known_recipes
local items = get_filtered_items(player, data)
data.discovered = data.known_recipes - oldknown
data.items_raw = items
search(data)
if singleplayer then
init_hud(player, data)
if data.show_hud == nil and data.discovered > 0 then
data.show_hud = true
end
end
end)

View File

@ -1,10 +1,13 @@
local replacements = {fuel = {}}
local http = ...
IMPORT("copy", "insert", "sort", "match", "sub")
IMPORT("true_str", "is_table", "valid_item", "table_merge", "table_replace", "table_eq")
IMPORT("fmt", "reg_items", "reg_aliases", "reg_nodes", "is_cube", "get_cube", "ItemStack")
IMPORT("is_group", "extract_groups", "item_has_groups", "groups_to_items", "get_group_stereotype")
local ItemStack = ItemStack
local fmt, reg_items, reg_aliases = i3.get("fmt", "reg_items", "reg_aliases")
local maxn, copy, insert, sort, match = i3.get("maxn", "copy", "insert", "sort", "match")
local is_group, extract_groups, item_has_groups, groups_to_items =
i3.get("is_group", "extract_groups", "item_has_groups", "groups_to_items")
local true_str, is_table, valid_item, table_merge, table_replace, rcp_eq =
i3.get("true_str", "is_table", "valid_item", "table_merge", "table_replace", "rcp_eq")
local function get_burntime(item)
return core.get_craft_result{method = "fuel", items = {item}}.time
@ -22,55 +25,10 @@ local function cache_fuel(item)
end
end
local function cache_groups(group, groups)
i3.groups[group] = {}
i3.groups[group].groups = groups
i3.groups[group].items = groups_to_items(groups)
if #groups == 1 then
i3.groups[group].stereotype = get_group_stereotype(groups[1])
end
local items = i3.groups[group].items
if #items <= 1 then return end
local px, lim, c = 64, 10, 0
local sprite = "[combine:WxH"
for _, item in ipairs(items) do
local def = reg_items[item]
local tiles = def.tiles or def.tile_images
local texture = true_str(def.inventory_image) and def.inventory_image --or tiles[1]
if def.drawtype and is_cube(def.drawtype) then
texture = get_cube(tiles)
end
if texture then
texture = texture:gsub("%^", "\\^"):gsub(":", "\\:") .. fmt("\\^[resize\\:%ux%u", px, px)
sprite = sprite .. fmt(":0,%u=%s", c * px, texture)
c++
if c == lim then break end
end
end
if c > 1 then
sprite = sprite:gsub("WxH", px .. "x" .. px * c)
i3.groups[group].sprite = sprite
i3.groups[group].count = c
end
end
local function get_item_usages(item, recipe, added)
if is_group(item) then
local group = item:sub(7)
local group_cache = i3.groups[group]
local groups = group_cache and group_cache.groups or extract_groups(item)
if not group_cache then
cache_groups(group, groups)
end
local groups = extract_groups(item)
if groups then
for name, def in pairs(reg_items) do
if not added[name] and valid_item(def) and item_has_groups(def.groups, groups) then
local usage = copy(recipe)
@ -145,7 +103,7 @@ local function drop_table(name, drop)
end
if not di.rarity then
count_sure++
count_sure = count_sure + 1
end
end
end
@ -178,7 +136,17 @@ local function cache_recipes(item)
_recipes[#recipes + 1 - k] = v
end
local shift = 0
local size_rpl = maxn(replacements[item])
local size_rcp = #_recipes
if size_rpl > size_rcp then
shift = size_rcp - size_rpl
end
for k, v in pairs(replacements[item]) do
k = k + shift
if _recipes[k] then
_recipes[k].replacements = v
end
@ -215,7 +183,7 @@ core.register_craft = function(def)
if is_group(output[1]) then
groups = extract_groups(output[1])
output = groups_to_items(groups)
output = groups_to_items(groups, true)
end
for i = 1, #output do
@ -238,7 +206,11 @@ local old_clear_craft = core.clear_craft
core.clear_craft = function(def)
old_clear_craft(def)
-- TODO: hide in crafting guide
if true_str(def) then
return -- TODO
elseif is_table(def) then
return -- TODO
end
end
local function resolve_aliases(hash)
@ -260,7 +232,7 @@ local function resolve_aliases(hash)
local rcp_new = copy(i3.recipes_cache[newname][j])
rcp_new.output = oldname
if table_eq(rcp_old, rcp_new) then
if rcp_eq(rcp_old, rcp_new) then
similar = true
break
end
@ -272,7 +244,7 @@ local function resolve_aliases(hash)
end
end
if newname ~= "" and i3.recipes_cache[oldname] and reg_items[newname] and not hash[newname] then
if newname ~= "" and i3.recipes_cache[oldname] and not hash[newname] then
insert(i3.init_items, newname)
end
end
@ -301,39 +273,17 @@ local function init_recipes()
resolve_aliases(_select)
sort(i3.init_items)
if http and true_str(i3.export_url) then
if i3.http and true_str(i3.export_url) then
local post_data = {
recipes = i3.recipes_cache,
usages = i3.usages_cache,
}
http.fetch_async {
i3.http.fetch_async {
url = i3.export_url,
post_data = core.write_json(post_data),
}
end
end
local function init_cubes()
for name, def in pairs(reg_nodes) do
if def then
local id = core.get_content_id(name)
local tiles = def.tiles or def.tile_images
if is_cube(def.drawtype) then
i3.cubes[id] = get_cube(tiles)
elseif sub(def.drawtype, 1, 9) == "plantlike" or sub(def.drawtype, 1, 8) == "firelike" then
local texture = true_str(def.inventory_image) and def.inventory_image or tiles[1]
if texture then
i3.plants[id] = texture
end
end
end
end
end
return function()
init_recipes()
init_cubes()
end
return init_recipes

View File

@ -1,17 +1,13 @@
local fmt = string.format
local PNG = {
blank = "i3_blank.png",
bg = "i3_bg.png",
bg_full = "i3_bg_full.png",
bg_goto = "i3_bg_goto.png",
bg_content = "i3_bg_content.png",
bar = "i3_bar.png",
hotbar = "i3_hotbar.png",
highlight = "i3_highlight.png",
search = "i3_search.png",
heart = "i3_heart.png",
heart_half = "i3_heart_half.png",
heart_grey = "i3_heart_grey.png",
prev = "i3_next.png^\\[transformFX",
next = "i3_next.png",
arrow = "i3_arrow.png",
@ -25,41 +21,28 @@ local PNG = {
book = "i3_book.png",
sign = "i3_sign.png",
cancel = "i3_cancel.png",
crafting = "i3_crafting.png",
slot = "i3_slot.png^\\[resize:128x128",
pagenum_hover = "i3_slot.png^\\[resize:128x128^\\[opacity:130",
export = "i3_export.png",
slot = "i3_slot.png",
tab = "i3_tab.png",
tab_small = "i3_tab_small.png",
tab_top = "i3_tab.png^\\[transformFY",
furnace_anim = "i3_furnace_anim.png",
shapeless = "i3_shapeless.png",
bag = "i3_bag.png",
armor = "i3_armor.png",
awards = "i3_award.png",
skins = "i3_skin.png",
waypoints = "i3_waypoint.png",
teleport = "i3_teleport.png",
add = "i3_add.png",
refresh = "i3_refresh.png",
visible = "i3_visible.png^\\[brighten",
nonvisible = "i3_non_visible.png",
exit = "i3_exit.png",
home = "i3_home.png",
flag = "i3_flag_anim.png",
edit = "i3_edit.png",
no_result = "i3_no_result.png",
find_more = "i3_find_more.png",
search_outline = "i3_search_outline.png",
search_outline_trim = "i3_search_outline_trim.png",
all = "i3_all.png",
node = "i3_node.png",
item = "i3_item.png",
cube = "i3_cube.png",
home_px = "i3_home_px.png",
home_px_hover = "i3_home_px_hover.png",
cancel_hover = "i3_cancel.png^\\[brighten",
search_hover = "i3_search.png^\\[brighten",
crafting_hover = "i3_crafting.png^\\[brighten",
export_hover = "i3_export.png^\\[brighten",
trash_hover = "i3_trash.png^\\[brighten^\\[colorize:#f00:100",
compress_hover = "i3_compress.png^\\[brighten",
sort_hover = "i3_sort.png^\\[brighten",
@ -74,38 +57,32 @@ local PNG = {
awards_hover = "i3_award_hover.png",
skins_hover = "i3_skin_hover.png",
waypoints_hover = "i3_waypoint_hover.png",
teleport_hover = "i3_teleport.png^\\[brighten",
add_hover = "i3_add.png^\\[brighten",
refresh_hover = "i3_refresh.png^\\[brighten",
exit_hover = "i3_exit.png^\\[brighten",
home_hover = "i3_home.png^\\[brighten",
edit_hover = "i3_edit.png^\\[brighten",
all_hover = "i3_all_on.png^\\[brighten",
node_hover = "i3_node_on.png^\\[brighten",
item_hover = "i3_item_on.png^\\[brighten",
}
local styles = string.format([[
listcolors[#bababa50;#bababa99]
style_type[list;size=1;spacing=0.15]
style_type[field;border=false;bgcolor=transparent]
style_type[label,field;font_size=16]
style_type[button;border=false;content_offset=0]
style_type[image_button,item_image_button,checkbox,dropdown;border=false;sound=i3_click]
style_type[item_image_button;bgimg_middle=9;padding=-9]
style_type[item_image_button:hovered;bgimg=%s]
style_type[item_image_button;bgimg_hovered=%s]
style[;sound=]
style[nofav;sound=i3_cannot]
style[search;content_offset=0]
style[pagenum,no_item,no_rcp;font=bold;font_size=18]
style[enable_search:hovered;bgimg=%s]
style[exit;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[cancel;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[prev_page,prev_recipe,prev_usage,prev_sort,prev_skin;fgimg=%s;fgimg_hovered=%s]
style[next_page,next_recipe,next_usage,next_sort,next_skin;fgimg=%s;fgimg_hovered=%s]
style[search;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[prev_page;fgimg=%s;fgimg_hovered=%s]
style[next_page;fgimg=%s;fgimg_hovered=%s]
style[prev_recipe;fgimg=%s;fgimg_hovered=%s]
style[next_recipe;fgimg=%s;fgimg_hovered=%s]
style[prev_usage;fgimg=%s;fgimg_hovered=%s]
style[next_usage;fgimg=%s;fgimg_hovered=%s]
style[waypoint_add;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[bag_rename;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[btn_bag,btn_armor,btn_skins;font=bold;font_size=18;content_offset=0;sound=i3_click]
style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
@ -116,25 +93,27 @@ local styles = string.format([[
style[confirm_trash_yes;sound=i3_trash]
]],
PNG.slot,
PNG.search_outline,
PNG.exit, PNG.exit_hover,
PNG.cancel, PNG.cancel_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.add, PNG.add_hover,
PNG.edit, PNG.edit_hover)
PNG.exit, PNG.exit_hover,
PNG.cancel, PNG.cancel_hover,
PNG.search, PNG.search_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.add, PNG.add_hover)
local fs_elements = {
label = "label[%f,%f;%s]",
box = "box[%f,%f;%f,%f;%s]",
image = "image[%f,%f;%f,%f;%s]",
tooltip = "tooltip[%f,%f;%f,%f;%s;#32333899;#fff]",
tooltip = "tooltip[%f,%f;%f,%f;%s]",
button = "button[%f,%f;%f,%f;%s;%s]",
checkbox = "checkbox[%f,%f;%s;%s;%s]",
slot = "image[%f,%f;%f,%f;" .. fmt("%s;9]", PNG.slot),
item_image = "item_image[%f,%f;%f,%f;%s]",
hypertext = "hypertext[%f,%f;%f,%f;%s;%s]",
bg9 = "background9[%f,%f;%f,%f;%s;false;12]",
bg9 = "background9[%f,%f;%f,%f;%s;false;%u]",
scrollbar = "scrollbar[%f,%f;%f,%f;%s;%s;%u]",
model = "model[%f,%f;%f,%f;%s;%s;%s;%s;%s;%s;%s]",
image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]",

View File

@ -11,59 +11,34 @@ mt3:get_meta():set_string("description", "Worn Pick")
mt3:get_meta():set_string("color", "yellow")
mt3:set_wear(10000)
minetest.register_craft {
minetest.register_craft({
output = mt:to_string(),
type = "shapeless",
recipe = {
"default:wood",
mt2:to_string(),
},
}
})
minetest.register_craft {
minetest.register_craft({
output = mt3:to_string(),
type = "shapeless",
recipe = {
"default:pick_mese",
"default:diamond",
},
}
minetest.clear_craft {
recipe = {
{"default:sand", "default:sand"},
{"default:sand", "default:sand"},
},
}
})
i3.register_craft {
url = "https://raw.githubusercontent.com/minetest-mods/i3/main/tests/test_online_recipe.json"
}
i3.register_craft {
i3.register_craft({
result = "default:ladder_wood 2",
items = {"default:copper_ingot 7, default:tin_ingot, default:steel_ingot 2"},
}
})
i3.register_craft {
result = "default:tree",
items = {
"default:wood",
"",
"default:wood"
},
}
i3.register_craft {
result = "default:cobble 16",
items = {
"default:stone, default:stone",
"default:stone, , default:stone",
", default:stone, default:stone",
}
}
i3.register_craft {
i3.register_craft({
grid = {
"X",
"#",
@ -75,9 +50,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X",
"#X",
@ -89,9 +64,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X",
},
@ -100,10 +75,10 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X#",
},
@ -112,9 +87,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X#X",
},
@ -123,9 +98,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X#XX",
},
@ -134,9 +109,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X#XX",
"X#X",
@ -146,9 +121,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X#XX",
"X#X",
@ -159,9 +134,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X##XX",
},
@ -170,9 +145,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X##X#X",
},
@ -181,9 +156,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X##X#X",
"",
@ -194,9 +169,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -208,9 +183,9 @@ i3.register_craft {
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -222,9 +197,9 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -237,9 +212,9 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -252,10 +227,10 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -269,9 +244,9 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -285,9 +260,9 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -301,9 +276,9 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -317,9 +292,9 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft {
i3.register_craft({
grid = {
"X #",
" ## ",
@ -335,4 +310,23 @@ i3.register_craft {
['X'] = "default:glass",
},
result = "default:mese 3",
}
})
i3.register_craft({
grid = {
"X #",
" ## ",
"X#X#",
"#X#X#",
"X X##X#X",
" ## ",
"#X#X#",
"#X#X#",
"X #",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass",
},
result = "default:mese 3",
})

View File

@ -1,26 +0,0 @@
local a, b, c = 0, 0, 0
b+=1
c++; local foo = "bar";
local t = {
a = a++,
b = 2,
c = c+=2,
d = a&3,
e = 1,
}
t["b"] <<= 4
t.b >>= 2
assert(t.b == 8)
--print(dump(t))
--c += 1
c*=2
local i = 16
i += i<<4
assert(i == 272)
assert((a+=2) == 2)
assert(c++ == 3)
assert((a-=1) == -1)
assert((c^=4) == 16)
assert((a&b) == 0)
assert((c|=a) == 2)
assert((1<<8) == 256)

View File

@ -1,50 +1,45 @@
local SWITCH
i3.new_tab("test1", {
description = "Test 1 Test 1",
image = "i3_heart.png",
formspec = function(player, data, fs)
fs("button", 3, 4, 3, 0.8, "test", "Click here")
fs("label", 3, 1, "Just a test")
if SWITCH then
fs"label[3,2;Button clicked]"
else
fs"label[3,2;Lorem Ipsum]"
end
fs("label[3,1;Test 1]")
end,
fields = function(player, data, fields)
if fields.test then
SWITCH = true
end
end
})
i3.new_tab("test2", {
description = "Test 2",
image = "i3_mesepick.png",
slots = true,
formspec = function(player, data, fs)
fs("label[3,1;Test 2]")
end,
})
i3.new_tab("test_creative", {
description = "Test creative",
i3.new_tab("test3", {
description = "Test 3",
access = function(player, data)
local name = player:get_player_name()
return core.is_creative_enabled(name)
if name == "singleplayer" then
return true
end
end,
formspec = function(player, data, fs)
fs("label[3,1;Creative enabled]")
fs("label[3,1;Test 3]")
end,
fields = i3.set_fs,
fields = function(player, data, fields)
i3.set_fs(player, "label[3,2;Test extra_fs]")
end,
})
i3.override_tab("test2", {
description = "Test override",
image = "i3_mesepick.png",
formspec = function(player, data, fs)
fs("label[3,1;Override!]")
end,
})

View File

@ -1,12 +0,0 @@
core.after(5, function()
i3.add_waypoint("Test", {
player = "singleplayer",
pos = {x = 0, y = 2, z = 0},
color = 0xffff00,
-- image = "heart.png",
})
core.after(5, function()
i3.remove_waypoint("singleplayer", "Test")
end)
end)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

BIN
textures/i3_book.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

BIN
textures/i3_export.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

BIN
textures/i3_heart_grey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
textures/i3_teleport.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

View File

@ -1,141 +0,0 @@
local exec = os.execute
local fmt, find, sub = string.format, string.find, string.sub
local var = "[%w%.%[%]\"\'_]"
local _,_, fs_elements = dofile("../src/styles.lua")
exec "clear"
local function split(str, delim, include_empty, max_splits, sep_is_pattern)
delim = delim or ","
max_splits = max_splits or -2
local items = {}
local pos, len = 1, #str
local plain = not sep_is_pattern
max_splits = max_splits + 1
repeat
local np, npe = find(str, delim, pos, plain)
np, npe = (np or (len+1)), (npe or (len+1))
if (not np) or (max_splits == 1) then
np = len + 1
npe = np
end
local s = sub(str, pos, np - 1)
if include_empty or (s ~= "") then
max_splits = max_splits - 1
items[#items + 1] = s
end
pos = npe + 1
until (max_splits == 0) or (pos > (len + 1))
return items
end
local files = {
"api",
"bags",
"caches",
"callbacks",
"common",
"compression",
"detached_inv",
"fields",
"groups",
"gui",
"hud",
"model_aliases",
"progressive",
"styles",
}
local operators = {
["([%+%-%*%^/&|])="] = function(a, b, c)
return fmt("%s = %s %s %s", a, a, b, c)
end,
["+%+"] = function(a, b)
return fmt("%s = %s + 1\n%s", a, a, b)
end,
["&"] = function(a, b)
return fmt("bit.band(%s, %s)", a, b)
end,
["|"] = function(a, b)
return fmt("bit.bor(%s, %s)", a, b)
end,
["<<"] = function(a, b)
return fmt("bit.lshift(%s, %s)", a, b)
end,
[">>"] = function(a, b)
return fmt("bit.rshift(%s, %s)", a, b)
end,
["<<="] = function(a, b)
return fmt("%s = bit.lshift(%s, %s)", a, a, b)
end,
[">>="] = function(a, b)
return fmt("%s = bit.rshift(%s, %s)", a, a, b)
end,
}
local function compile(data)
data = data:gsub("IMPORT%((.-)%)", function(a)
return "local " .. a:gsub("\"", "") .. " = i3.get(" .. a .. ")"
end)
data = data:gsub("([%w_]+)%(", function(a)
if fs_elements[a] then
return fmt("fs('%s',", a)
end
end)
data = data:gsub("([%w_]+)-%-\n", function(a)
return fmt("%s = %s - 1", a, a)
end)
for op, func in pairs(operators) do
data = data:gsub("(" .. var .. "+)%s?" .. op .. "%s?(" .. var .. "*)", func)
end
return data
end
for _, p in ipairs(files) do
local function _load(path, line, data, t)
if line then
if not t then
t = split(data, "\n")
end
t[line] = t[line]:gsub("(" .. var .. "+)%s?=%s?(" .. var .. "*)", "%2")
data = table.concat(t, "\n")
else
local file = assert(io.open(path, "r"))
data = file:read"*a"
file:close()
data = compile(data)
end
local l, err = loadstring(data)
if not l then
local err_line = tonumber(err:match(":(%d+):"))
if line ~= err_line then
return _load(path, err_line, data, t)
end
end
local _file = io.open(path:match("(.*)%.") .. ".lc", "w")
_file:write(data)
_file:close()
end
_load("../src/" .. p .. ".lua")
end
exec "luacheck ../init.lua"
exec "luacheck ../src/preprocessor.lua"
exec "luacheck ../src/*.lc"
exec "rm ../src/*.lc"

View File

@ -1,7 +0,0 @@
#!/bin/bash
# Colors with 0 alpha need to be preserved, because opaque leaves ignore alpha.
# For that purpose, the use of indexed colors is disabled (-nc).
cd ../textures
find -name '*.png' -print0 | xargs -0 optipng -o7 -zm1-9 -nc -strip all -clobber

View File

@ -1,23 +0,0 @@
local JSON = require"JSON" -- luarocks install json-lua
os.execute "clear"
local list = io.popen("curl -s -H 'Accept: text/html' http://servers.minetest.net/list"):read("*a")
list = JSON:decode(list).list
local servers = {}
for _, server in ipairs(list) do
if server.mods then
for _, mod in ipairs(server.mods) do
if mod == "i3" then
table.insert(servers, server.name)
end
end
end
end
if #servers > 0 then
print(("=> %u/%u servers using [i3]:\n\t• %s"):format(#servers, #list, table.concat(servers, "\n\t")))
else
print"No server using [i3]"
end