Compare commits

..

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

33 changed files with 480 additions and 898 deletions

View File

@ -31,7 +31,6 @@ exclude_files = {
"tests/test_custom_recipes.lua",
"tests/test_operators.lua",
"tests/test_tabs.lua",
"tests/test_waypoints.lua",
".install",
".luarocks",

136
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,34 +12,23 @@ 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"
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
do_things()
end
-- To prevent a formspec update, return false.
-- Otherwise: no need to return anything, it's automatic.
end,
})
```
@ -75,7 +51,7 @@ Return 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)`
@ -88,7 +64,7 @@ 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.
@ -146,7 +122,7 @@ i3.register_craft {
}
```
Multiple recipes can also be registered at once:
Multiples recipes can also be registered:
```Lua
i3.register_craft {
@ -180,53 +156,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
@ -372,54 +301,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.
Show a Steam-like HUD notification on the bottom-right corner of the screen (experimental).
- `name` is the player name.
- `msg` is the HUD message to show.
@ -441,4 +327,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,6 +1,6 @@
![logo](https://user-images.githubusercontent.com/7883281/145490041-d91d6bd6-a654-438d-b208-4d5736845ab7.png)
[![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) [![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.
@ -35,6 +35,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

View File

@ -20,7 +20,7 @@ local function lf(path)
end
i3 = {
version = 1161,
version = 1115,
data = core.deserialize(storage:get_string"data") or {},
settings = {
@ -33,11 +33,12 @@ i3 = {
wielditem_fade_after = 3,
save_interval = 600, -- Player data save interval (in seconds)
hud_speed = 3,
hud_timer_max = 3,
hud_speed = 1,
hud_timer_max = 1.5,
damage_enabled = core.settings:get_bool"enable_damage",
damage_enabled = core.settings:get_bool"enable_damage",
progressive_mode = core.settings:get_bool"i3_progressive_mode",
item_compression = core.settings:get_bool("i3_item_compression", true),
},
categories = {
@ -51,28 +52,15 @@ i3 = {
saves = { -- Metadata to save
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,
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",
@ -101,7 +89,6 @@ i3 = {
groups = {},
plants = {},
modules = {},
minitabs = {},
craft_types = {},
recipe_filters = {},
@ -112,8 +99,6 @@ i3 = {
i3.files.common()
i3.files.api(http)
i3.files.compress()
i3.files.detached()
i3.files.fields()
i3.files.groups()
i3.files.callbacks(http, storage)
@ -123,8 +108,7 @@ 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_operators.lua")()
lf("/tests/test_compression.lua")()
lf("/tests/test_custom_recipes.lua")()
end

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.

View File

@ -1,10 +1,10 @@
local http = ...
local make_fs, get_inventory_fs = i3.files.gui()
IMPORT("sorter", "sort_inventory", "play_sound")
IMPORT("gmatch", "split")
IMPORT("S", "err", "fmt", "reg_items")
IMPORT("sorter", "sort_inventory")
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")
function i3.register_craft_type(name, def)
@ -196,7 +196,7 @@ end
i3.new_tab("inventory", {
description = S"Inventory",
formspec = get_inventory_fs,
slots = true,
fields = i3.files.fields(),
})
function i3.remove_tab(name)
@ -204,10 +204,10 @@ function i3.remove_tab(name)
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
@ -315,34 +315,16 @@ function i3.hud_notif(name, msg, img)
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
data.show_hud = true
data.hud_msg = msg
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
if img then
data.hud_img = fmt("%s^[resize:16x16", img)
end
end
@ -374,126 +356,3 @@ i3.add_sorting_method("numerical", {
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,7 +1,7 @@
local replacements = {fuel = {}}
local http = ...
IMPORT("copy", "insert", "sort", "match", "sub")
IMPORT("maxn", "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")
@ -178,7 +178,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 += shift
if _recipes[k] then
_recipes[k].replacements = v
end

View File

@ -1,6 +1,7 @@
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
@ -203,15 +204,6 @@ local function init_data(player, info)
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
@ -220,11 +212,17 @@ local function init_data(player, info)
data.items_raw = i3.init_items
data.favs = {}
data.show_setting = "home"
data.ignore_hotbar = false
data.auto_sorting = false
data.reverse_sorting = false
data.inv_compress = true
data.crafting_counts = {}
data.sort = 1
data.tab = 1
data.itab = 1
data.subcat = 1
data.scrbar_inv = 0
data.font_size = data.font_size or 0
data.lang_code = get_lang_code(info)
data.fs_version = info.formspec_version
@ -261,6 +259,7 @@ insert(core.registered_on_joinplayers, 1, function(player)
init_data(player, info)
init_bags(player)
init_detached(player)
init_hud(player)
end)

View File

@ -1,8 +1,8 @@
local vec = vector.new
local ItemStack = ItemStack
local loadstring = loadstring
local reg_items = core.registered_items
local translate = core.get_translated_string
local vec_new, vec_add, vec_mul = vector.new, vector.add, vector.multiply
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 =
@ -327,12 +327,8 @@ 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.settings.item_compression and not next(i3.recipe_filters) and data.filter == ""
end
local function compressible(item, data)
@ -360,8 +356,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,7 +376,7 @@ 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 = vec_add(ppos, vec_mul(dir, 1))
core.add_item(look_at, stack)
end
@ -408,11 +409,6 @@ 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"
@ -427,42 +423,32 @@ local function craft_stack(player, data, craft_rcp)
if is_group(name) then
items = {}
local item_groups = get_group_items(name)
local groups = extract_groups(name)
local groupname = name:sub(7)
local item_groups = i3.groups[groupname].items or groups_to_items(groups)
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.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
end
end
end
for item, v in pairs(items) do
for _ = 1, v * scrbar_val do
inv:remove_item("main", item)
for k, v in pairs(items) do
inv:remove_item("main", fmt("%s %s", k, v * scrbar_val))
end
end
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
if rcp_def.replacements then
for _, pair in ipairs(rcp_def.replacements) do
get_stack(player, ItemStack(pair[2]))
end
end
@ -487,9 +473,9 @@ local function safe_teleport(player, pos)
play_sound(name, "i3_teleport", 0.8)
local vel = player:get_velocity()
player:add_velocity(-vel)
player:add_velocity(vec_mul(vel, -1))
local p = vec(pos)
local p = vec_new(pos)
p.y += 0.25
player:set_pos(p)
@ -577,7 +563,11 @@ local function sort_inventory(player, data)
local size = inv:get_size"main"
local start_i = data.ignore_hotbar and (data.hotbar_len + 1) or 1
list = data.inv_compress and compress_items(list, start_i) or pre_sorting(list, start_i)
if data.inv_compress then
list = compress_items(list, start_i)
else
list = pre_sorting(list, start_i)
end
local new_inv = i3.sorting_methods[data.sort].func(list, data)
if not new_inv then return end
@ -606,8 +596,8 @@ local function reset_data(data)
data.goto_page = nil
data.recipes = nil
data.usages = nil
data.crafting_rcp = nil
data.crafting_usg = nil
data.crafting_rcp = nil
data.crafting_usg = nil
data.alt_items = nil
data.confirm_trash = nil
data.show_settings = nil
@ -619,54 +609,17 @@ local function reset_data(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",
@ -684,7 +637,11 @@ local function update_inv_size(player, data)
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")
if data.legacy_inventory then
player:hud_set_hotbar_image"gui_hotbar.png"
else
player:hud_set_hotbar_image"i3_hotbar.png"
end
end)
end
@ -727,7 +684,6 @@ local _ = {
get_recipes = get_recipes,
sort_inventory = sort_inventory,
sort_by_category = sort_by_category,
recipe_filter_set = recipe_filter_set,
apply_recipe_filters = apply_recipe_filters,
-- Type checks
@ -752,7 +708,6 @@ local _ = {
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
@ -818,7 +773,6 @@ local _ = {
-- Math
round = round,
abs = math.abs,
min = math.min,
max = math.max,
ceil = math.ceil,
@ -826,8 +780,12 @@ local _ = {
random = math.random,
-- Vectors
vec = vector.new,
vec_new = vector.new,
vec_add = vector.add,
vec_sub = vector.subtract,
vec_mul = vector.multiply,
vec_round = vector.round,
vec_eq = vector.equals,
}
function i3.get(...)

View File

@ -1,5 +1,5 @@
local set_fs = i3.set_fs
IMPORT("play_sound", "create_inventory")
IMPORT("fmt", "play_sound", "create_inventory")
local trash = create_inventory("i3_trash", {
allow_put = function(_, _, _, stack)
@ -22,3 +22,15 @@ local trash = create_inventory("i3_trash", {
})
trash:set_size("main", 1)
local function init_detached(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_detached

View File

@ -1,22 +1,22 @@
local set_fs = i3.set_fs
IMPORT("min", "max", "vec_round")
IMPORT("reg_items", "reg_aliases")
IMPORT("min", "max", "vec_eq", "vec_round")
IMPORT("S", "random", "translate", "ItemStack")
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")
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 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+$")
if sb_inv and sub(sb_inv, 1, 3) == "CHG" then
data.scrbar_inv = tonumber(match(sb_inv, "%d+"))
return
end
for field in pairs(fields) do
@ -34,8 +34,6 @@ local function inv_fields(player, data, fields)
if str == "legacy_inventory" then
update_inv_size(player, data)
elseif str == "collapse" then
search(data)
end
elseif sub(field, 1, 8) == "setting_" then
@ -44,8 +42,6 @@ local function inv_fields(player, data, fields)
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
@ -132,6 +128,9 @@ local function inv_fields(player, data, fields)
elseif fields.sort then
sort_inventory(player, data)
elseif fields.dd_sorting_method then
data.sort = tonumber(fields.dd_sorting_method)
elseif fields.home then
if not data.home then
return msg(name, "No home set")
@ -148,6 +147,9 @@ local function inv_fields(player, data, fields)
elseif fields.bag_rename then
data.bag_rename = true
elseif fields.sb_font_size then
data.font_size = tonumber(fields.sb_font_size:match"-?%d+$")
elseif fields.confirm_rename then
local bag = get_detached_inv("bag", name)
local bagstack = bag:get_stack("main", 1)
@ -178,7 +180,7 @@ local function inv_fields(player, data, fields)
local pos = player:get_pos()
for _, v in ipairs(data.waypoints) do
if vec_round(pos) == vec_round(str_to_pos(v.pos)) then
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 have set a waypoint at this position")
end
@ -205,6 +207,8 @@ local function inv_fields(player, data, fields)
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)
@ -286,13 +290,13 @@ local function select_item(player, data, fields)
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.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
@ -423,7 +427,8 @@ 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)")
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
@ -439,12 +444,6 @@ core.register_on_player_receive_fields(function(player, formname, 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)
@ -461,16 +460,12 @@ core.register_on_player_receive_fields(function(player, formname, fields)
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
if tab and tab.fields then
return true, tab.fields(player, data, fields)
end
return true, set_fs(player)
end)
return inv_fields

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
IMPORT("max", "ceil", "remove", "str_to_pos")
IMPORT("get_connected_players", "add_hud_waypoint")
IMPORT("ceil", "get_connected_players", "str_to_pos", "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
@ -13,7 +13,33 @@ local function init_hud(player)
end
data.hud = {
notifs = {},
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,
},
img = 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 = "",
z_index = 0xDEAD,
},
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,
},
wielditem = player:hud_add {
hud_elem_type = "text",
@ -24,55 +50,54 @@ local function init_hud(player)
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)
-- It would better to have an engine function `hud_move` to only need
-- 2 calls for the notification's back and forth.
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
local hud_info_bg = player:hud_get(data.hud.bg)
local dt = 0.016
if offset.y < notif.max.y then
notif.show = false
notif.hud_timer += dt
if hud_info_bg.position.y <= 0.9 then
data.show_hud = false
data.hud_timer = (data.hud_timer or 0) + dt
end
player:hud_change(notif.elems.text, "text", notif.hud_msg)
player:hud_change(data.hud.text, "text", data.hud_msg)
if notif.hud_img then
player:hud_change(notif.elems.img, "text", notif.hud_img)
if data.hud_img then
player:hud_change(data.hud.img, "text", data.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
if data.show_hud then
for _, def in pairs(data.hud) 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))
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y - ((dt / 5) * i3.settings.hud_speed)
})
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)
elseif data.show_hud == false 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, "offset", {
x = hud_info.offset.x - speed,
y = hud_info.offset.y
})
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y + ((dt / 5) * i3.settings.hud_speed)
})
end
if hud_info.offset.x < notif.max.x then
player:hud_remove(def)
remove(data.hud.notifs, idx)
if hud_info_bg.position.y >= 1 then
data.show_hud = nil
data.hud_timer = nil
data.hud_msg = nil
data.hud_img = nil
end
end
end
@ -88,18 +113,8 @@ core.register_globalstep(function(dt)
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
player:hud_change(data.hud.wielditem, "text", "")
return
end
@ -107,7 +122,7 @@ core.register_globalstep(function(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
if data.timer >= i3.settings.wielditem_fade_after then
player:hud_change(data.hud.wielditem, "text", "")
end
return
@ -128,6 +143,21 @@ core.register_globalstep(function(dt)
end
end)
core.register_globalstep(function()
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 data and data.show_hud ~= nil then
show_hud(player, data)
end
end
end)
local function init_waypoints(player)
local name = player:get_player_name()
local data = i3.data[name]
@ -135,7 +165,7 @@ local function init_waypoints(player)
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)
local id = add_hud_waypoint(player, v.name, str_to_pos(v.pos), v.color)
v.id = id
end
end

View File

@ -81,10 +81,6 @@ local function _load(path, line, data, t)
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

View File

@ -2,22 +2,11 @@ local set_fs = i3.set_fs
local hud_notif = i3.hud_notif
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
})
IMPORT("fmt", "search", "table_merge", "array_diff")
IMPORT("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters")
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,7 +17,8 @@ local function get_filtered_items(player, data)
usages = #apply_recipe_filters(usages or {}, player)
if recipes > 0 or usages > 0 then
items[item] = true
c++
items[c] = item
known += recipes + usages
end
end
@ -143,20 +133,13 @@ local function poll_new_items(player, data, join)
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
if reg_nodes[last_discovered] then
local id = core.get_content_id(last_discovered)
img = i3.cubes[id] or img
end
hud_notif(data.player_name, msg, img)
hud_notif(data.player_name, msg, "i3_book.png")
end
data.items_progress = items
data.items_raw = items
data.itab = 1
sort_by_category(data)
search(data)
set_fs(player)
end

View File

@ -12,6 +12,7 @@ local 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",
@ -44,7 +45,7 @@ local PNG = {
nonvisible = "i3_non_visible.png",
exit = "i3_exit.png",
home = "i3_home.png",
flag = "i3_flag_anim.png",
flag = "i3_flag.png",
edit = "i3_edit.png",
no_result = "i3_no_result.png",
find_more = "i3_find_more.png",
@ -53,9 +54,6 @@ local 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",
@ -85,9 +83,6 @@ local PNG = {
}
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]
@ -100,6 +95,7 @@ local styles = string.format([[
style[search;content_offset=0]
style[pagenum,no_item,no_rcp;font=bold;font_size=18]
style[enable_search:hovered;bgimg=%s]
style[enable_search:pressed;bgimg=%s^[opacity:178]
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]
@ -116,7 +112,7 @@ local styles = string.format([[
style[confirm_trash_yes;sound=i3_trash]
]],
PNG.slot,
PNG.search_outline,
PNG.search_outline, PNG.search_outline,
PNG.exit, PNG.exit_hover,
PNG.cancel, PNG.cancel_hover,
PNG.prev, PNG.prev_hover,
@ -128,7 +124,7 @@ 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),

View File

@ -1,32 +1,15 @@
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]")

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: 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: 404 B

BIN
textures/i3_flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

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: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B