Compare commits

..

35 Commits
1.5.1 ... 1.6.2

Author SHA1 Message Date
e1c0f106cc Fix Luacheck warnings on Ubuntu 22.04 2022-04-26 01:50:25 +02:00
46f1136bb7 Empty slots don't make click sound 2022-04-25 19:24:24 +02:00
e88921fe72 Fix crash 2022-04-25 16:00:49 +02:00
7e0feefc89 API doc: minor correction 2022-03-23 03:52:08 +01:00
44a6256589 Fix tab access on priv revoking 2022-03-21 03:55:22 +01:00
1dd742e887 HUD: minor tweak 2022-03-12 04:30:20 +01:00
518ed971ca Hide pagenum when only one page of skins 2022-03-01 12:15:28 +01:00
e9b8085fde Tweak quick crafting 2022-02-27 19:13:27 +01:00
6dc12390db Tweak sorting 2022-02-27 18:53:26 +01:00
44610b879e Skin selection UI overhaul 2022-02-26 02:09:53 +01:00
b0c9bcf3b9 Update screenshot 2022-02-21 01:02:57 +01:00
de70846d6c Smoother scrolling in skins category 2022-02-20 20:38:51 +01:00
8b6f50b387 Fix potential crash with skinssb 2022-02-20 18:57:52 +01:00
55c6d09389 Wording 2022-01-31 18:09:53 +01:00
e3941a7b71 Add max_waypoints setting 2022-01-16 18:16:56 +01:00
5e8ecf9903 Add util/ 2022-01-15 04:06:31 +01:00
2fa971acb4 Fix missing func 2022-01-11 02:49:44 +01:00
617ef40a1d Small cleanup 2022-01-09 03:29:10 +01:00
6b8e64b532 Split more stuff into different file 2022-01-08 17:33:01 +01:00
eedc77086f Some cleaning 2022-01-08 16:40:10 +01:00
ea10743255 mod.conf fix 2022-01-08 15:59:01 +01:00
4fe094f3ba Add min_version in mod.conf 2022-01-06 13:35:18 +01:00
364534d154 Debug mode: more benchmark precision 2022-01-05 02:12:51 +01:00
11e19dd80a Update issue templates 2022-01-04 16:30:35 +01:00
f0d1caa1b4 Small cleaning 2022-01-04 15:38:59 +01:00
b48c7862dc Add debug_mode setting + cleanup 2022-01-02 14:51:53 +01:00
d8e17687e1 A bit of cleaning 2021-12-25 17:24:30 +01:00
2fadcdefdd Fix variable name 2021-12-25 17:01:25 +01:00
c7f6e1db62 API: fix custom recipe registration 2021-12-24 00:43:38 +01:00
16a1865e11 More cleaning 2021-12-21 17:16:46 +01:00
748cc9a7a1 Small cleaning 2021-12-21 15:39:04 +01:00
edf0867e6c Don't use bitops for 5.4 clients 2021-12-15 13:45:19 +01:00
7275767427 Fix crash with certain mods enabled (address #40) 2021-12-14 21:51:02 +01:00
c9f7e0a40e Standardize global setting names 2021-12-13 00:34:15 +01:00
1f4cec3420 Put back GitHub workflow 2021-12-10 00:35:58 +01:00
21 changed files with 996 additions and 884 deletions

24
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,24 @@
---
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?

21
.github/workflows/luacheck.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Luacheck
on: [push, pull_request]
jobs:
luacheck:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup lua
uses: leafo/gh-actions-lua@v8
with:
luaVersion: 5.1
- name: Setup luarocks
uses: leafo/gh-actions-luarocks@v4
- name: Setup luacheck
run: luarocks install luacheck
- name: Run luacheck linter
run: cd util; lua luacheck.lua

6
API.md
View File

@ -17,9 +17,7 @@ i3.new_tab("stuff", {
-- 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()
if name == "singleplayer" then
return false
end
return name == "singleplayer"
end,
formspec = function(player, data, fs)
@ -280,7 +278,7 @@ A table containing all sorting methods.
Adds a new group of items to compress.
- `item` is the item that serve as stereotype for the group of compressed items.
- `item` is the item which represent the group of compressed items.
- `def` is a table specifying the substring replace patterns to be used.
Example:

View File

@ -1,6 +1,6 @@
![logo](https://user-images.githubusercontent.com/7883281/145490041-d91d6bd6-a654-438d-b208-4d5736845ab7.png)
[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) [![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)
[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) [![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)
#### **`i3`** is a next-generation inventory for Minetest.
@ -53,4 +53,4 @@ 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/144105848-535b5e4f-a6fc-44d6-add1-e62a45d36f63.png)
![Preview](https://content.minetest.net/uploads/3abf3755de.png)

239
init.lua
View File

@ -12,6 +12,7 @@ print[[
local modpath = core.get_modpath"i3"
local http = core.request_http_api()
local storage = core.get_mod_storage()
local _loadfile = dofile(modpath .. "/src/operators.lua")
local function lf(path)
@ -19,17 +20,27 @@ local function lf(path)
end
i3 = {
modules = {},
MAX_FAVS = 6,
ITEM_BTN_SIZE = 1.1,
DROP_BAG_ON_DIE = true,
MIN_FORMSPEC_VERSION = 4,
SAVE_INTERVAL = 600, -- Player data save interval (in seconds)
data = core.deserialize(storage:get_string"data") or {},
HUD_TIMER_MAX = 1.5,
HUD_SPEED = 1,
settings = {
debug_mode = false,
max_favs = 6,
max_waypoints = 30,
min_fs_version = 4,
item_btn_size = 1.1,
drop_bag_on_die = true,
save_interval = 600, -- Player data save interval (in seconds)
SUBCAT = {
hud_speed = 1,
hud_timer_max = 1.5,
damage_enabled = core.settings:get_bool"enable_damage",
progressive_mode = core.settings:get_bool"i3_progressive_mode",
legacy_inventory = core.settings:get_bool"i3_legacy_inventory",
item_compression = core.settings:get_bool("i3_item_compression", true),
},
categories = {
"bag",
"armor",
"skins",
@ -37,7 +48,7 @@ i3 = {
"waypoints",
},
META_SAVES = {
saves = { -- Metadata to save
bag = true,
home = true,
waypoints = true,
@ -46,21 +57,6 @@ i3 = {
known_recipes = true,
},
-- Caches
init_items = {},
fuel_cache = {},
usages_cache = {},
recipes_cache = {},
cubes = {},
plants = {},
tabs = {},
craft_types = {},
recipe_filters = {},
search_filters = {},
sorting_methods = {},
files = {
api = lf"/src/api.lua",
bags = lf"/src/bags.lua",
@ -69,189 +65,48 @@ i3 = {
common = lf"/src/common.lua",
compress = lf"/src/compress.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",
tests = {
tabs = lf"/tests/test_tabs.lua",
operators = lf"/tests/test_operators.lua",
compression = lf"/tests/test_compression.lua",
custom_recipes = lf"/tests/test_custom_recipes.lua",
}
},
progressive_mode = core.settings:get_bool"i3_progressive_mode",
legacy_inventory = core.settings:get_bool"i3_legacy_inventory",
item_compression = core.settings:get_bool("i3_item_compression", true),
-- Caches
init_items = {},
fuel_cache = {},
usages_cache = {},
recipes_cache = {},
tabs = {},
cubes = {},
plants = {},
modules = {},
craft_types = {},
recipe_filters = {},
search_filters = {},
sorting_methods = {},
}
i3.HOTBAR_LEN = i3.legacy_inventory and 8 or 9
i3.INV_SIZE = 4 * i3.HOTBAR_LEN
i3.settings.hotbar_len = i3.settings.legacy_inventory and 8 or 9
i3.settings.inv_size = 4 * i3.settings.hotbar_len
i3.files.common()
i3.files.api(http)
i3.files.compress()
i3.files.groups()
i3.files.callbacks()
i3.files.callbacks(http, storage)
local storage = core.get_mod_storage()
local slz, dslz, copy = i3.get("slz", "dslz", "copy")
local set_fs = i3.set_fs
i3.data = dslz(storage:get_string"data") or {}
local init_bags = i3.files.bags()
local init_detached = i3.files.detached()
local fill_caches = i3.files.caches(http)
local init_hud = i3.files.hud()
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 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()
fill_caches()
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_detached(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.SAVE_INTERVAL, routine)
end
core.after(i3.SAVE_INTERVAL, routine)
if i3.progressive_mode then
if i3.settings.progressive_mode then
i3.files.progressive()
end
--i3.files.tests.tabs()
--i3.files.tests.operators()
--i3.files.tests.compression()
--i3.files.tests.custom_recipes(http)
if i3.settings.debug_mode then
lf("/tests/test_tabs.lua")()
lf("/tests/test_operators.lua")()
lf("/tests/test_compression.lua")()
lf("/tests/test_custom_recipes.lua")()
end

View File

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

View File

@ -1,5 +1,5 @@
local make_fs = i3.files.gui()
local http = ...
local make_fs, get_inventory_fs = i3.files.gui()
IMPORT("gmatch", "split")
IMPORT("S", "err", "fmt", "reg_items")
@ -22,7 +22,7 @@ end
function i3.register_craft(def)
local width, c = 0, 0
if true_str(def.url) then
if http and true_str(def.url) then
http.fetch({url = def.url}, function(result)
if result.succeeded then
local t = core.parse_json(result.data)
@ -51,7 +51,7 @@ function i3.register_craft(def)
def.result = nil
end
if not true_str(def.output) then
if not true_str(def.output) and not def.url then
return err "i3.register_craft: output missing"
end
@ -69,9 +69,7 @@ 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]
@ -86,26 +84,29 @@ function i3.register_craft(def)
def.items[c] = def.key[symbol]
end
else
local items, len = def.items, #def.items
local items = copy(def.items)
local lines = {}
def.items = {}
for i = 1, len do
local rlen = #split(items[i], ",")
for i = 1, #items do
lines[i] = split(items[i], ",", true)
if rlen > width then
width = rlen
if #lines[i] > width then
width = #lines[i]
end
end
for i = 1, len do
while #split(items[i], ",") < width do
items[i] = fmt("%s,", items[i])
for i = 1, #items do
while #lines[i] < width do
insert(lines[i], items[i])
end
end
for name in gmatch(concat(items, ","), "[%s%w_:]+") do
c++
def.items[c] = clean_name(name)
for _, line in ipairs(lines) do
for _, v in ipairs(line) do
c++
def.items[c] = clean_name(v)
end
end
end
@ -166,6 +167,13 @@ 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
@ -185,6 +193,12 @@ function i3.new_tab(name, def)
insert(i3.tabs, def)
end
i3.new_tab("inventory", {
description = S"Inventory",
formspec = get_inventory_fs,
fields = i3.files.fields(),
})
function i3.remove_tab(name)
if not true_str(name) then
return err "i3.remove_tab: tab name missing"
@ -214,8 +228,8 @@ function i3.set_tab(player, tabname)
return
end
for i, def in ipairs(i3.tabs) do
if def.name == tabname then
for i, tab in ipairs(i3.tabs) do
if tab.name == tabname then
data.tab = i
return
end

View File

@ -1,3 +1,4 @@
local PNG = i3.files.styles()
local replacements = {fuel = {}}
local http = ...
@ -241,7 +242,7 @@ local function resolve_aliases(hash)
end
end
if newname ~= "" and i3.recipes_cache[oldname] and not hash[newname] then
if newname ~= "" and i3.recipes_cache[oldname] and reg_items[newname] and not hash[newname] then
insert(i3.init_items, newname)
end
end
@ -285,29 +286,25 @@ end
local function get_cube(tiles)
if not true_table(tiles) then
return "i3_blank.png"
return PNG.blank
end
local t = copy(tiles)
local texture
for k, v in pairs(t) do
if type(v) == "table" then
t[k] = v.name
end
local top = tiles[1] or PNG.blank
if is_table(top) then
top = top.name or top.image
end
-- Tiles: up, down, right, left, back, front
-- Inventory cube: up, front, right
if #t <= 2 then
texture = draw_cube(t[1], t[1], t[1])
elseif #t <= 5 then
texture = draw_cube(t[1], t[3], t[3])
else -- Full tileset
texture = draw_cube(t[1], t[6], t[3])
local left = tiles[3] or top or PNG.blank
if is_table(left) then
left = left.name or left.image
end
return texture
local right = tiles[5] or left or PNG.blank
if is_table(right) then
right = right.name or right.image
end
return draw_cube(top, left, right)
end
local function init_cubes()

View File

@ -1,467 +1,12 @@
local _, get_inventory_fs = i3.files.gui()
local http, storage = ...
local init_bags = i3.files.bags()
local fill_caches = i3.files.caches(http)
local init_detached = i3.files.detached()
local init_hud = i3.files.hud()
local set_fs = i3.set_fs
IMPORT("vec_eq", "vec_round")
IMPORT("reg_items", "reg_aliases")
IMPORT("sort", "copy", "insert", "remove", "indexof")
IMPORT("S", "min", "random", "translate", "ItemStack")
IMPORT("fmt", "find", "match", "sub", "lower", "split", "toupper")
IMPORT("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound", "spawn_item")
IMPORT("search", "get_sorting_idx", "sort_inventory", "sort_by_category", "get_recipes", "get_detached_inv")
IMPORT("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 == "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
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.prev_sort or fields.next_sort then
local idx = get_sorting_idx(data.sort)
local tot = #i3.sorting_methods
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 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 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 += 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++
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 -= (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)
IMPORT("slz", "min", "insert", "copy", "ItemStack")
IMPORT("spawn_item", "reset_data", "get_detached_inv")
core.register_on_player_hpchange(function(player, hpchange)
local name = player:get_player_name()
@ -479,7 +24,7 @@ core.register_on_dieplayer(function(player)
local data = i3.data[name]
if not data then return end
if i3.DROP_BAG_ON_DIE then
if i3.settings.drop_bag_on_die then
local bagstack = ItemStack(data.bag)
spawn_item(player, bagstack)
end
@ -520,3 +65,141 @@ core.register_on_player_inventory_action(function(player, _, _, info)
set_fs(player)
end
end)
if core.global_exists("armor") then
i3.modules.armor = true
armor:register_on_update(set_fs)
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]
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.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.settings.inv_size)
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_detached(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

@ -101,7 +101,7 @@ 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 and def.description)) or ""
local desc = lower(translate(data.lang_code, def.description)) or ""
local search_in = fmt("%s %s", item, desc)
local temp, j, to_add = {}, 1
@ -282,24 +282,19 @@ local function apply_recipe_filters(recipes, player)
end
local function compression_active(data)
return i3.item_compression and not next(i3.recipe_filters) and data.filter == ""
return i3.settings.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(favs, query_item)
local fav, i
for j = 1, #favs do
if favs[j] == query_item then
fav = true
i = j
break
local function is_fav(data)
for i = 1, #data.favs do
if data.favs[i] == data.query_item then
return i
end
end
return fav, i
end
local function sort_by_category(data)
@ -519,14 +514,17 @@ local function compress_items(list, start_i)
return new_inv
end
local function drop_items(player, inv, list, start_i, rej)
local function drop_items(player, inv, list, start_i, rej, remove)
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)
if not remove then
spawn_item(player, stack)
end
inv:set_stack("main", i, ItemStack(""))
end
end
@ -539,10 +537,10 @@ 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 (i3.HOTBAR_LEN + 1) or 1
local start_i = data.ignore_hotbar and (i3.settings.hotbar_len + 1) or 1
if true_table(data.drop_items) then
list = drop_items(player, inv, list, start_i, data.drop_items)
list = drop_items(player, inv, list, start_i, data.drop_items, true)
end
if data.inv_compress then
@ -566,11 +564,35 @@ 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.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
local function add_hud_waypoint(player, name, pos, color)
return player:hud_add {
hud_elem_type = "waypoint",
name = name,
text = " m",
text = "m",
world_pos = pos,
number = color,
z_index = -300,
@ -643,6 +665,7 @@ local _ = {
spawn_item = spawn_item,
clean_name = clean_name,
play_sound = play_sound,
reset_data = reset_data,
safe_teleport = safe_teleport,
add_hud_waypoint = add_hud_waypoint,

View File

@ -277,13 +277,7 @@ for _, nodename in ipairs(v) do
t[nodename] = {}
for _, shape in ipairs(circular_saw_names) do
local to_add = true
if shape[1] == "slope" and shape[2] == "" then
to_add = nil
end
if to_add then
if shape[1] ~= "slope" or shape[2] ~= "" then
insert(t[nodename], fmt("%s_%s%s", shape[1], nodename, shape[2]))
end
end
@ -292,7 +286,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

456
src/fields.lua Normal file
View File

@ -0,0 +1,456 @@
local set_fs = i3.set_fs
IMPORT("vec_eq", "vec_round")
IMPORT("reg_items", "reg_aliases")
IMPORT("S", "random", "translate", "ItemStack")
IMPORT("sort", "copy", "insert", "remove", "indexof")
IMPORT("fmt", "find", "match", "sub", "lower", "split", "toupper")
IMPORT("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound", "reset_data")
IMPORT("search", "get_sorting_idx", "sort_inventory", "sort_by_category", "get_recipes", "get_detached_inv")
IMPORT("valid_item", "get_stack", "craft_stack", "clean_name", "compressible", "check_privs", "safe_teleport")
local function inv_fields(player, data, fields)
local name = data.player_name
local inv = player:get_inventory()
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
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.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
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)
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
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.prev_sort or fields.next_sort then
local idx = get_sorting_idx(data.sort)
local tot = #i3.sorting_methods
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 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_eq(vec_round(pos), vec_round(str_to_pos(v.pos))) then
play_sound(name, "i3_cannot", 0.8)
return msg(name, S"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 += 1000
elseif fields.hide_debug_grid then
data.hide_debug_grid = not data.hide_debug_grid
end
return set_fs(player)
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.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 -= (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.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 = 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.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 Minetest client is up-to-date (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.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)
return inv_fields

View File

@ -1,4 +1,6 @@
local damage_enabled = core.settings:get_bool"enable_damage"
local damage_enabled = i3.settings.damage_enabled
local hotbar_len = i3.settings.hotbar_len
local debug_mode = i3.settings.debug_mode
local model_aliases = i3.files.model_alias()
local PNG, styles, fs_elements, colors = i3.files.styles()
@ -6,9 +8,9 @@ local PNG, styles, fs_elements, colors = i3.files.styles()
local sprintf = string.format
local VoxelArea, VoxelManip = VoxelArea, VoxelManip
IMPORT("clr", "ESC", "check_privs")
IMPORT("find", "match", "sub", "upper")
IMPORT("vec_new", "vec_sub", "vec_round")
IMPORT("clr", "ESC", "msg", "check_privs")
IMPORT("min", "max", "floor", "ceil", "round")
IMPORT("reg_items", "reg_tools", "reg_entities")
IMPORT("get_bag_description", "get_detached_inv")
@ -122,22 +124,22 @@ local function get_stack_max(inv, data, is_recipe, rcp)
end
local function get_inv_slots(fs)
local inv_x = i3.legacy_inventory and 0.75 or 0.22
local inv_x = i3.settings.legacy_inventory and 0.75 or 0.22
local inv_y = 6.9
local size, spacing = 1, 0.1
fs"style_type[box;colors=#77777710,#77777710,#777,#777]"
for i = 0, i3.HOTBAR_LEN - 1 do
for i = 0, hotbar_len - 1 do
fs("box", i * size + inv_x + (i * spacing), inv_y, size, size, "")
end
fs(fmt("style_type[list;size=%f;spacing=%f]", size, spacing),
fmt("list[current_player;main;%f,%f;%u,1;]", inv_x, inv_y, i3.HOTBAR_LEN))
fmt("list[current_player;main;%f,%f;%u,1;]", inv_x, inv_y, hotbar_len))
fs(fmt("style_type[list;size=%f;spacing=%f]", size, spacing),
fmt("list[current_player;main;%f,%f;%u,%u;%u]", inv_x, inv_y + 1.15,
i3.HOTBAR_LEN, i3.INV_SIZE / i3.HOTBAR_LEN, i3.HOTBAR_LEN),
hotbar_len, i3.settings.inv_size / hotbar_len, hotbar_len),
"style_type[list;size=1;spacing=0.15]")
fs"listring[current_player;craft]listring[current_player;main]"
@ -233,14 +235,14 @@ local function get_isometric_view(fs, pos, X, Y, t, cubes, depth, high)
local width = 8
local base_height = 4
local base_depth = depth == -1
local max_depth = -5
local max_depth = -7
local height = base_depth and (base_height - 1) or depth
local pos1 = vec_new(pos.x - width, pos.y + depth, pos.z - width)
local pos2 = vec_new(pos.x + width, pos.y + height, pos.z + width)
local vm = VoxelManip(pos1, pos2)
local emin, emax = vm:read_from_map(pos1, pos2)
local emin, emax = vm:get_emerged_area()
local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
local data = vm:get_data()
@ -270,30 +272,30 @@ local function get_isometric_view(fs, pos, X, Y, t, cubes, depth, high)
end
end
local maxc = ((width << 1) ^ 2) * base_height
local maxc = ((width * 2) ^ 2) * base_height
if cubes < maxc and depth > max_depth then
-- if there's not enough map to preview, go deeper
depth -= 1
get_isometric_view(fs, pos, X, Y, t, cubes, depth, high)
else
local shift = -0.3 - high
return get_isometric_view(fs, pos, X, Y, t, cubes, depth, high)
end
for i = max_depth, 0 do
local dth = t[i]
if dth then
dth[0] = #dth
for j = 1, dth[0] do
local params = dth[j]
params[2] += shift
insert(fs, fmt("image[%f,%f;%.1f,%.1f;%s]", unpack(params)))
end
local shift = -0.3 - high
for i = max_depth, 0 do
local dth = t[i]
if dth then
dth[0] = #dth
for j = 1, dth[0] do
local params = dth[j]
params[2] += shift
insert(fs, fmt("image[%f,%f;%.1f,%.1f;%s]", unpack(params)))
end
end
shift += (base_depth and 0.45 or 0.95)
fs("image", 2.7, Y + shift, 0.3, 0.3, PNG.flag)
end
shift += (base_depth and 0.45 or 0.95)
fs("image", 2.7, Y + shift, 0.3, 0.3, PNG.flag)
end
local function get_waypoint_fs(fs, data, player, yextra, ctn_len)
@ -454,7 +456,7 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa
local yextra = damage_enabled and 5.5 or 5
for i, title in ipairs(i3.SUBCAT) do
for i, title in ipairs(i3.categories) do
local btn_name = fmt("btn_%s", title)
fs(fmt("style[btn_%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]", title,
data.subcat == i and PNG[fmt("%s_hover", title)] or PNG[title],
@ -492,19 +494,44 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa
local _skins = skins.get_skinlist_for_player(name)
local skin_name = skins.get_player_skin(player).name
local sks, id = {}, 1
local spp, add_y = 24, 0
for i, skin in ipairs(_skins) do
if skin.name == skin_name then
id = i
end
if #_skins > spp then
local btn_y = yextra + 0.75
add_y += 0.6
insert(sks, skin.name)
data.skin_pagemax = max(1, ceil(#_skins / spp))
fs("image_button", 1.5, btn_y, 0.35, 0.35, "", "prev_skin", "")
fs("image_button", 3.85, btn_y, 0.35, 0.35, "", "next_skin", "")
fs"style[skin_page;font=bold;font_size=18]"
fs("button", 1.85, btn_y - 0.23, 2, 0.8, "skin_page",
fmt("%s / %u", clr(colors.yellow, data.skin_pagenum), data.skin_pagemax))
end
sks = concat(sks, ","):gsub(";", "")
fs("label", 0, yextra + 0.85, fmt("%s:", ES"Select a skin"))
fs(fmt("dropdown[0,%f;4,0.6;skins;%s;%u;true]", yextra + 1.1, sks, id))
local first = (data.skin_pagenum - 1) * spp
local last = first + spp - 1
for i = first, last do
local skin = _skins[i + 1]
if not skin then break end
local btn_name = fmt("skin_btn_%u", i + 1)
fs(fmt([[ style[%s;padding=10;
fgimg=%s;bgimg=%s;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6] ]],
btn_name, skin:get_preview(),
skin.name == skin_name and "i3_btn9_hovered.png" or "i3_btn9.png"))
local X = (i % 3) * 1.93
local Y = ceil((i % spp - X) / 3 + 1)
Y += (Y * 2.45) + yextra - 2.75 + add_y
fs("image_button", X, Y, 1.86, 3.4, "", btn_name, "")
fs(fmt("tooltip[%s;%s]", btn_name, ESC(skin.name)))
end
elseif data.subcat == 4 then
if not i3.modules.awards then
@ -577,9 +604,6 @@ local function show_popup(fs, data)
elseif show_sorting then
fs("button", 2.1, 9.7, 6, 0.8, "select_sorting", ES"Select the inventory sorting method:")
fs(fmt("style[prev_sort;fgimg=%s;fgimg_hovered=%s]", PNG.prev, PNG.prev_hover),
fmt("style[next_sort;fgimg=%s;fgimg_hovered=%s]", PNG.next, PNG.next_hover))
fs("image_button", 2.2, 10.6, 0.35, 0.35, "", "prev_sort", "")
fs("image_button", 7.65, 10.6, 0.35, 0.35, "", "next_sort", "")
@ -604,7 +628,7 @@ local function show_popup(fs, data)
end
fs("style[drop_items;font_size=15;font=mono;textcolor=#dbeeff]",
fmt("field[5.4,10.68;2.4,0.45;drop_items;Drop items:;%s]",
fmt("field[5.4,10.68;2.4,0.45;drop_items;Remove items:;%s]",
ESC(concat(data.drop_items or {}, ","))),
"field_close_on_enter[drop_items;false]")
@ -617,7 +641,7 @@ local function show_popup(fs, data)
fmt("tooltip[cb_auto_sorting;%s;#707070;#fff]",
ES"Enable this option to sort your inventory automatically"),
fmt("tooltip[drop_items;%s;#707070;#fff]",
"Add a comma-separated list of items to drop on inventory sorting.\n" ..
"Add a comma-separated list of items to remove on inventory sorting.\n" ..
"Format: " .. ("mod:item,mod:item, ..."):gsub("(%a+:%a+)", clr("#bddeff", "%1"))))
end
end
@ -665,6 +689,14 @@ local function get_inventory_fs(player, data, fs)
max_val += 10
elseif i3.modules.skins and data.subcat == 3 then
local spp = 24
local _skins = skins.get_skinlist_for_player(data.player_name)
local nb = #_skins
local num = max(1, min(spp, nb - ((data.skin_pagenum - 1) * spp)))
max_val += ceil(num / 3) * (nb > spp and 34 or 31)
elseif i3.modules.awards and data.subcat == 4 then
award_list = awards.get_award_states(data.player_name)
award_list_nb = #award_list
@ -790,13 +822,14 @@ local function get_tooltip(item, info, pos)
end
if pos then
return fmt("tooltip", pos.x, pos.y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, ESC(tooltip))
local btn_size = i3.settings.item_btn_size
return fmt("tooltip", pos.x, pos.y, btn_size, btn_size, ESC(tooltip))
end
return fmt("tooltip[%s;%s]", item, ESC(tooltip))
end
local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_size, _btn_size)
local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_size, btn_size2)
local custom_recipe = i3.craft_types[rcp.type]
local cooking = rcp.type == "cooking"
local fuel = rcp.type == "fuel"
@ -832,14 +865,15 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz
end
end
local arrow_X = right + 0.2 + (_btn_size or i3.ITEM_BTN_SIZE)
local BTN_SIZE = i3.settings.item_btn_size
local arrow_X = right + 0.2 + (btn_size2 or BTN_SIZE)
local X = arrow_X + 1.2
local Y = data.yoffset + 1.4
fs("image", arrow_X, Y + 0.06, 1, 1, PNG.arrow)
if fuel then
fs("animated_image", X + 0.05, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, PNG.fire_anim, 8, 180)
fs("animated_image", X + 0.05, Y, BTN_SIZE, BTN_SIZE, PNG.fire_anim, 8, 180)
return
end
@ -848,17 +882,17 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz
local name = item:get_name()
local count = item:get_count()
local wear = item:get_wear()
local bt_s = i3.ITEM_BTN_SIZE * 1.2
local bt_s = BTN_SIZE * 1.2
local _name = fmt("_%s", name)
local pos
if meta:get_string"color" ~= "" or meta:get_string"palette_index" ~= "" then
local rcp_usg = is_recipe and "rcp" or "usg"
fs(fmt("style_type[list;size=%f]", i3.ITEM_BTN_SIZE))
fs(fmt("style_type[list;size=%f]", BTN_SIZE))
fs"listcolors[#bababa50;#bababa99]"
fs(fmt("list[detached:i3_output_%s_%s;main;%f,%f;1,1;]", rcp_usg, data.player_name, X + 0.11, Y))
fs("button", X + 0.11, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, _name, "")
fs("button", X + 0.11, Y, BTN_SIZE, BTN_SIZE, _name, "")
local inv = get_detached_inv(fmt("output_%s", rcp_usg), data.player_name)
inv:set_stack("main", 1, item)
@ -866,7 +900,7 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz
else
fs("image", X, Y - 0.11, bt_s, bt_s, PNG.slot)
fs("item_image_button",
X + 0.11, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE,
X + 0.11, Y, BTN_SIZE, BTN_SIZE,
fmt("%s %u %u", name, count * (is_recipe and data.scrbar_rcp or data.scrbar_usg or 1), wear),
_name, "")
end
@ -898,7 +932,8 @@ end
local function get_grid_fs(fs, data, rcp, is_recipe)
local width = rcp.width or 1
local right, btn_size, _btn_size = 0, i3.ITEM_BTN_SIZE
local right = 0
local btn_size, _btn_size = i3.settings.item_btn_size
local cooktime, shapeless
if rcp.type == "cooking" then
@ -1089,8 +1124,7 @@ local function get_model_fs(fs, data, def, model_alias)
hex = "0" .. hex
end
_name = fmt("%s^[multiply:%s", v.name,
fmt("#%s%s", sub(hex, 3), sub(hex, 1, 2)))
_name = fmt("%s^[multiply:%s", v.name, fmt("#%s%s", sub(hex, 3), sub(hex, 1, 2)))
else
_name = fmt("%s^[multiply:%s", v.name, v.color)
end
@ -1111,11 +1145,12 @@ local function get_model_fs(fs, data, def, model_alias)
end
local function get_header(fs, data)
local fav = is_fav(data.favs, data.query_item)
local fav = is_fav(data)
local nfavs = #data.favs
local max_favs = i3.settings.max_favs
local star_x, star_y, size = data.inv_width + 0.3, data.yoffset + 0.2, 0.4
if nfavs < i3.MAX_FAVS or (nfavs == i3.MAX_FAVS and fav) then
if nfavs < max_favs or (nfavs == max_favs and fav) then
local fav_marked = fmt("i3_fav%s.png", fav and "_off" or "")
fs(fmt("style[fav;fgimg=%s;fgimg_hovered=%s;fgimg_pressed=%s]",
fmt("i3_fav%s.png", fav and "" or "_off"), fav_marked, fav_marked))
@ -1191,12 +1226,13 @@ local function get_export_fs(fs, data, is_recipe, is_usage, max_stacks_rcp, max_
fs(fmt("style[scrbar_%s;noclip=true]", name),
fmt("scrollbaroptions[min=1;max=%u;smallstep=1]", craft_max))
fs("scrollbar", data.inv_width + 8.1, data.yoffset, 3, 0.35, "horizontal", fmt("scrbar_%s", name), stack_fs)
fs("button", data.inv_width + 8.1, data.yoffset + 0.4, 3, 0.7,
fmt("craft_%s", name), ES("Craft (×@1)", stack_fs))
local x = data.inv_width + 8.1
fs("scrollbar", x, data.yoffset, 2.5, 0.35, "horizontal", fmt("scrbar_%s", name), stack_fs)
fs("button", x, data.yoffset + 0.4, 2.5, 0.7, fmt("craft_%s", name), ES("Craft (×@1)", stack_fs))
end
local function get_rcp_extra(fs, player, data, panel, is_recipe, is_usage)
local function get_rcp_extra(fs, data, player, panel, is_recipe, is_usage)
fs"container[0,0.075]"
local rn = panel.rcp and #panel.rcp
@ -1265,7 +1301,7 @@ local function hide_items(player, data)
end
end
local function get_items_fs(fs, player, data, full_height)
local function get_items_fs(fs, data, player, full_height)
hide_items(player, data)
local items = data.alt_items or data.items or {}
@ -1273,6 +1309,8 @@ local function get_items_fs(fs, player, data, full_height)
local ipp = rows * lines
local size = 0.85
fs("bg9", data.inv_width + 0.1, 0, 7.9, full_height, PNG.bg_full, 10)
fs(fmt("box[%f,0.2;4.05,0.6;#bababa25]", data.inv_width + 0.3),
"set_focus[filter]",
fmt("field[%f,0.2;2.95,0.6;filter;;%s]", data.inv_width + 0.35, ESC(data.filter)),
@ -1307,10 +1345,10 @@ local function get_items_fs(fs, player, data, full_height)
local name = _compressed and item:sub(2) or item
local X = i % rows
X -= (X * 0.045) + data.inv_width + 0.28
X -= (X * 0.045) + data.inv_width + 0.28
local Y = round((i % ipp - X) / rows + 1, 0)
Y -= (Y * 0.085) + 0.95
Y -= (Y * 0.085) + 0.95
insert(fs, fmt("item_image_button", X, Y, size, size, name, item, ""))
@ -1342,6 +1380,7 @@ local function get_items_fs(fs, player, data, full_height)
end
local function get_favs(fs, data)
local btn_size = i3.settings.item_btn_size
fs("label", data.inv_width + 0.4, data.yoffset + 0.4, ES"Bookmarks")
for i = 1, #data.favs do
@ -1350,50 +1389,30 @@ local function get_favs(fs, data)
local Y = data.yoffset + 0.8
if data.query_item == item then
fs("image", X, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, PNG.slot)
fs("image", X, Y, btn_size, btn_size, PNG.slot)
end
fs("item_image_button", X, Y, i3.ITEM_BTN_SIZE, i3.ITEM_BTN_SIZE, item, item, "")
fs("item_image_button", X, Y, btn_size, btn_size, item, item, "")
end
end
local function get_panels(fs, player, data, full_height)
local _title = {name = "title", height = 1.4}
local _favs = {name = "favs", height = 2.23}
local _items = {name = "items", height = full_height}
local _recipes = {name = "recipes", rcp = data.recipes, height = 4.045}
local _usages = {name = "usages", rcp = data.usages, height = 4.045}
local panels
local function get_panels(fs, data, player)
local title = {name = "title", height = 1.4, func = get_header}
local favs = {name = "favs", height = 2.23, func = get_favs}
local recipes = {name = "recipes", rcp = data.recipes, height = 4.045, func = get_rcp_extra}
local usages = {name = "usages", rcp = data.usages, height = 4.045, func = get_rcp_extra}
local panels = {title, recipes, usages, favs}
data.yoffset = 0
if data.query_item then
panels = {_title, _recipes, _usages, _favs}
else
panels = {_items}
end
for idx = 1, #panels do
local panel = panels[idx]
data.yoffset = 0
if idx > 1 then
for _idx = idx - 1, 1, -1 do
data.yoffset = data.yoffset + panels[_idx].height + 0.1
end
for i, panel in ipairs(panels) do
if i > 1 then
data.yoffset += panels[i - 1].height + 0.1
end
fs("bg9", data.inv_width + 0.1, data.yoffset, 7.9, panel.height, PNG.bg_full, 10)
local is_recipe, is_usage = panel.name == "recipes", panel.name == "usages"
if is_recipe or is_usage then
get_rcp_extra(fs, player, data, panel, is_recipe, is_usage)
elseif panel.name == "items" then
get_items_fs(fs, player, data, full_height)
elseif panel.name == "title" then
get_header(fs, data)
elseif panel.name == "favs" then
get_favs(fs, data)
end
panel.func(fs, data, player, panel, is_recipe, is_usage)
end
end
@ -1446,6 +1465,10 @@ local function get_tabs_fs(fs, player, data, full_height)
end
local function get_debug_grid(data, fs, full_height)
fs"style[hide_debug_grid;noclip=true]"
fs("button", -2, full_height - 1, 2, 1, "hide_debug_grid", "Toggle grid")
if data.hide_debug_grid then return end
fs("style_type[label;font_size=8;noclip=true]")
local spacing, i = 0.2, 1
@ -1469,7 +1492,7 @@ local function get_debug_grid(data, fs, full_height)
end
local function make_fs(player, data)
--local start = os.clock()
local start = debug_mode and core.get_us_time() or nil
local fs = setmetatable({}, {
__call = function(t, ...)
@ -1487,26 +1510,40 @@ local function make_fs(player, data)
data.inv_width = 10.23
local full_height = 12
local tab = i3.tabs[data.tab]
fs(fmt("formspec_version[%u]size[%f,%f]no_prepend[]bgcolor[#0000]",
i3.MIN_FORMSPEC_VERSION, data.inv_width + 8, full_height), styles)
i3.settings.min_fs_version, data.inv_width + 8, full_height), styles)
fs("bg9", 0, 0, data.inv_width, full_height, PNG.bg_full, 10)
local tab = i3.tabs[data.tab]
if tab then
tab.formspec(player, data, fs)
end
get_panels(fs, player, data, full_height)
if data.query_item then
get_panels(fs, data, player)
else
get_items_fs(fs, data, player, full_height)
end
if #i3.tabs > 1 then
local visible_tabs = 1
for _, def in ipairs(i3.tabs) do
if visible_tabs < 2 and def.access and def.access(player, data) then
visible_tabs++
end
end
if visible_tabs > 1 then
get_tabs_fs(fs, player, data, full_height)
end
--get_debug_grid(data, fs, full_height)
--print("make_fs()", fmt("%.2f ms", (os.clock() - start) * 1000))
--print("#fs elements", #fs)
if debug_mode then
get_debug_grid(data, fs, full_height)
msg(data.player_name, fmt("make_fs(): %.2f ms", (core.get_us_time() - start) / 1000))
msg(data.player_name, fmt("#fs elements: %u", #fs))
end
return concat(fs)
end

View File

@ -34,9 +34,9 @@ local function init_hud(player)
},
}
if not i3.legacy_inventory then
if not i3.settings.legacy_inventory then
core.after(0, function()
player:hud_set_hotbar_itemcount(i3.HOTBAR_LEN)
player:hud_set_hotbar_itemcount(i3.settings.hotbar_len)
player:hud_set_hotbar_image"i3_hotbar.png"
end)
end
@ -66,18 +66,18 @@ local function show_hud(player, data)
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y - ((dt / 5) * i3.HUD_SPEED)
y = hud_info.position.y - ((dt / 5) * i3.settings.hud_speed)
})
end
elseif data.show_hud == false then
if data.hud_timer >= i3.HUD_TIMER_MAX then
if data.hud_timer >= i3.settings.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) * i3.HUD_SPEED)
y = hud_info.position.y + ((dt / 5) * i3.settings.hud_speed)
})
end

View File

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

View File

@ -74,17 +74,14 @@ local styles = string.format([[
style_type[image_button,item_image_button,checkbox,dropdown;border=false;sound=i3_click]
style_type[item_image_button;bgimg_hovered=%s]
style[;sound=]
style[nofav;sound=i3_cannot]
style[pagenum,no_item,no_rcp;font=bold;font_size=18]
style[exit;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[cancel;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[search;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[prev_page;fgimg=%s;fgimg_hovered=%s]
style[next_page;fgimg=%s;fgimg_hovered=%s]
style[prev_recipe;fgimg=%s;fgimg_hovered=%s]
style[next_recipe;fgimg=%s;fgimg_hovered=%s]
style[prev_usage;fgimg=%s;fgimg_hovered=%s]
style[next_usage;fgimg=%s;fgimg_hovered=%s]
style[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[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]
@ -102,10 +99,6 @@ 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,
PNG.edit, PNG.edit_hover)

View File

@ -38,6 +38,24 @@ i3.register_craft({
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({
grid = {
"X",
@ -311,22 +329,3 @@ i3.register_craft({
},
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

@ -16,30 +16,18 @@ i3.new_tab("test2", {
end,
})
i3.new_tab("test3", {
description = "Test 3",
i3.new_tab("test_creative", {
description = "Test creative",
access = function(player, data)
local name = player:get_player_name()
if name == "singleplayer" then
return true
end
return core.is_creative_enabled(name)
end,
formspec = function(player, data, fs)
fs("label[3,1;Test 3]")
fs("label[3,1;Creative enabled]")
end,
fields = function(player, data, fields)
i3.set_fs(player, "label[3,2;Test extra_fs]")
end,
fields = i3.set_fs,
})
i3.override_tab("test2", {
description = "Test override",
image = "i3_mesepick.png",
formspec = function(player, data, fs)
fs("label[3,1;Override!]")
end,
})

View File

@ -2,7 +2,7 @@ local exec = os.execute
local fmt, find, sub = string.format, string.find, string.sub
local var = "[%w%.%[%]\"\'_]"
exec("reset")
exec "clear"
local function split(str, delim, include_empty, max_splits, sep_is_pattern)
delim = delim or ","
@ -36,6 +36,7 @@ local files = {
"common",
"compress",
"detached_inv",
"fields",
"groups",
"gui",
"hud",
@ -120,10 +121,10 @@ for _, p in ipairs(files) do
_file:close()
end
_load("./src/" .. p .. ".lua")
_load("../src/" .. p .. ".lua")
end
exec("luacheck init.lua")
exec("luacheck ./src/operators.lua")
exec("luacheck ./src/*.l")
exec("rm ./src/*.l")
exec "luacheck ../init.lua"
exec "luacheck ../src/operators.lua"
exec "luacheck ../src/*.l"
exec "rm ../src/*.l"

7
util/optipng.sh Normal file
View File

@ -0,0 +1,7 @@
#!/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

23
util/servers.lua Normal file
View File

@ -0,0 +1,23 @@
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