Compare commits

..

28 Commits
1.12.3 ... 1.15

Author SHA1 Message Date
66b88dec3b Bump version 2023-02-05 19:01:03 +01:00
c207db38a0 Minor tweak 2023-02-02 19:29:35 +01:00
c89cbf700f New crafting sound 2023-02-02 03:17:48 +01:00
a1a4535f26 Quick Crafting: indicate the missing materials when you cannot craft 2023-02-02 02:20:42 +01:00
80b927de1c Improve set home button look 2023-02-01 16:15:37 +01:00
482027d09b Small cleanup 2023-02-01 15:06:36 +01:00
ea7140a7bb Bugfix 2023-02-01 04:23:28 +01:00
84416b130b Minor cleaning 2023-02-01 04:22:03 +01:00
b31e7a48ab API: Add field to toggle slots in custom tabs 2023-02-01 04:08:52 +01:00
7a4f352ce4 Limit HUDCHANGE pkt sending 2023-01-29 21:47:13 +01:00
675349bd40 Bump version 2023-01-22 14:20:04 +01:00
e17f4e4c29 HUD: minor tweak 2023-01-21 19:18:40 +01:00
3b1d569118 Attempt to Quick Crafting replacements (again) 2023-01-21 00:43:38 +01:00
d55994c535 Minor fix 2023-01-20 21:57:27 +01:00
b6b97aa284 Fix group replacements in Quick Crafting 2023-01-20 21:20:12 +01:00
b4482f0acf Update MIT License 2023-01-20 16:07:28 +01:00
e918942439 Minor fix 2023-01-20 16:06:25 +01:00
889259ac5e Fix Quick Crafting bugs 2023-01-20 14:39:34 +01:00
187b0339bd Minor changes to API 2023-01-19 18:57:16 +01:00
74e88acadf Don't allow removing the 'All' tab 2023-01-19 18:34:43 +01:00
9e1f608846 Small fix 2023-01-19 18:03:56 +01:00
8289d290a3 Fix HUD 2023-01-19 17:58:24 +01:00
d833199628 API.md: Add summary 2023-01-19 16:50:01 +01:00
ac7a1fb746 Complete minitabs API 2023-01-19 16:40:31 +01:00
b95b179a5a Add an API to add minitabs 2023-01-19 01:24:45 +01:00
38f1d7c960 Bump version 2023-01-11 00:10:30 +01:00
5d4f9b4709 Get rid of Factorio's sounds 2023-01-11 00:07:27 +01:00
a0a3394e18 Add API to manage waypoints 2023-01-07 12:47:45 +01:00
21 changed files with 547 additions and 171 deletions

View File

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

112
API.md
View File

@ -1,6 +1,19 @@
## API # API :screwdriver:
### Custom tabs ### 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
#### `i3.new_tab(name, def)` #### `i3.new_tab(name, def)`
@ -13,6 +26,7 @@ Custom tabs can be added to the `i3` inventory as follow (example):
i3.new_tab("stuff", { i3.new_tab("stuff", {
description = "Stuff", description = "Stuff",
image = "image.png", -- Optional, add an image next to the tab description 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.
-- --
-- The functions below are all optional -- The functions below are all optional
@ -36,7 +50,7 @@ i3.new_tab("stuff", {
-- Do things -- Do things
end end
i3.set_fs(player) -- Update the formspec, mandatory -- No need to update the formspec, it's automatic
end, end,
}) })
``` ```
@ -72,7 +86,7 @@ A list of registered tabs.
--- ---
### Custom recipes ### Recipes
Custom recipes are nonconventional crafts outside the main crafting grid. Custom recipes are nonconventional crafts outside the main crafting grid.
They can be registered in-game dynamically and have a size beyond 3x3 items. They can be registered in-game dynamically and have a size beyond 3x3 items.
@ -164,6 +178,53 @@ 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.minimap`
A list of registered minitabs.
---
### Recipe filters ### Recipe filters
Recipe filters can be used to filter the recipes shown to players. Progressive Recipe filters can be used to filter the recipes shown to players. Progressive
@ -309,6 +370,49 @@ 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 ### Miscellaneous
#### `i3.hud_notif(name, msg[, img])` #### `i3.hud_notif(name, msg[, img])`

12
LICENSE
View File

@ -3,7 +3,7 @@ License of source code
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2020-2021 Jean-Patrick Guerrero and contributors. Copyright (c) 2020-2023 Jean-Patrick Guerrero and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -23,6 +23,16 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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) Licenses of media (textures)
---------------------------- ----------------------------

View File

@ -1,6 +1,6 @@
![logo](https://user-images.githubusercontent.com/7883281/145490041-d91d6bd6-a654-438d-b208-4d5736845ab7.png) ![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)]() ![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) [![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. #### **`i3`** is a next-generation inventory for Minetest.

View File

@ -20,7 +20,7 @@ local function lf(path)
end end
i3 = { i3 = {
version = 1123, version = 115,
data = core.deserialize(storage:get_string"data") or {}, data = core.deserialize(storage:get_string"data") or {},
settings = { settings = {
@ -34,7 +34,7 @@ i3 = {
save_interval = 600, -- Player data save interval (in seconds) save_interval = 600, -- Player data save interval (in seconds)
hud_speed = 1, hud_speed = 1,
hud_timer_max = 1.5, hud_timer_max = 3,
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", progressive_mode = core.settings:get_bool"i3_progressive_mode",
@ -101,6 +101,7 @@ i3 = {
groups = {}, groups = {},
plants = {}, plants = {},
modules = {}, modules = {},
minitabs = {},
craft_types = {}, craft_types = {},
recipe_filters = {}, recipe_filters = {},
@ -112,6 +113,7 @@ i3.files.common()
i3.files.api(http) i3.files.api(http)
i3.files.compress() i3.files.compress()
i3.files.detached() i3.files.detached()
i3.files.fields()
i3.files.groups() i3.files.groups()
i3.files.callbacks(http, storage) i3.files.callbacks(http, storage)
@ -121,6 +123,7 @@ end
if i3.settings.debug_mode then if i3.settings.debug_mode then
lf("/tests/test_tabs.lua")() 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_compression.lua")()
lf("/tests/test_custom_recipes.lua")() lf("/tests/test_custom_recipes.lua")()

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 http = ...
local make_fs, get_inventory_fs = i3.files.gui() local make_fs, get_inventory_fs = i3.files.gui()
IMPORT("gmatch", "split")
IMPORT("S", "err", "fmt", "reg_items")
IMPORT("sorter", "sort_inventory", "play_sound") IMPORT("sorter", "sort_inventory", "play_sound")
IMPORT("get_player_by_name", "add_hud_waypoint")
IMPORT("sort", "concat", "copy", "insert", "remove") IMPORT("sort", "concat", "copy", "insert", "remove")
IMPORT("gmatch", "split", "S", "err", "fmt", "reg_items", "pos_to_str")
IMPORT("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name") IMPORT("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name")
function i3.register_craft_type(name, def) function i3.register_craft_type(name, def)
@ -196,7 +196,7 @@ end
i3.new_tab("inventory", { i3.new_tab("inventory", {
description = S"Inventory", description = S"Inventory",
formspec = get_inventory_fs, formspec = get_inventory_fs,
fields = i3.files.fields(), slots = true,
}) })
function i3.remove_tab(name) function i3.remove_tab(name)
@ -204,10 +204,10 @@ function i3.remove_tab(name)
return err "i3.remove_tab: tab name missing" return err "i3.remove_tab: tab name missing"
end end
for i, def in ipairs(i3.tabs) do for i = #i3.tabs, 2, -1 do
if name == def.name then local def = i3.tabs[i]
if def and name == def.name then
remove(i3.tabs, i) remove(i3.tabs, i)
break
end end
end end
end end
@ -315,7 +315,6 @@ function i3.hud_notif(name, msg, img)
end end
local data = i3.data[name] local data = i3.data[name]
if not data then if not data then
return err "i3.hud_notif: no player data initialized" return err "i3.hud_notif: no player data initialized"
end end
@ -358,3 +357,126 @@ i3.add_sorting_method("numerical", {
return list return list
end, 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

@ -178,13 +178,9 @@ local function cache_recipes(item)
_recipes[#recipes + 1 - k] = v _recipes[#recipes + 1 - k] = v
end end
local shift = 0
local size_rpl = maxn(replacements[item]) local size_rpl = maxn(replacements[item])
local size_rcp = #_recipes local size_rcp = #_recipes
local shift = size_rcp - size_rpl
if size_rpl > size_rcp then
shift = size_rcp - size_rpl
end
for k, v in pairs(replacements[item]) do for k, v in pairs(replacements[item]) do
k += shift k += shift

View File

@ -360,13 +360,8 @@ local function sort_by_category(data)
for i = 1, #items do for i = 1, #items do
local item = items[i] local item = items[i]
local to_add = true local tab = i3.minitabs[data.itab]
local to_add = tab.sorter(item, data)
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 if to_add then
insert(new, item) insert(new, item)
@ -413,6 +408,11 @@ local function get_stack(player, stack)
end end
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 function craft_stack(player, data, craft_rcp)
local inv = player:get_inventory() local inv = player:get_inventory()
local rcp_usg = craft_rcp and "recipe" or "usage" local rcp_usg = craft_rcp and "recipe" or "usage"
@ -427,32 +427,42 @@ local function craft_stack(player, data, craft_rcp)
if is_group(name) then if is_group(name) then
items = {} items = {}
local groups = extract_groups(name) local item_groups = get_group_items(name)
local groupname = name:sub(7)
local item_groups = i3.groups[groupname].items or groups_to_items(groups)
local remaining = count local remaining = count
for _, item in ipairs(item_groups) do for _, item in ipairs(item_groups) do
for _name, _count in pairs(data.crafting_counts[rcp_usg].inv) do for _name, _count in pairs(data.crafting_counts[rcp_usg].inv) do
if item == _name and remaining > 0 then if item == _name and remaining > 0 then
local c = min(remaining, _count) local c = min(remaining, _count)
items[item] = c items[item] = c
remaining -= c remaining -= c
end
if remaining == 0 then break end
end end
if remaining == 0 then break end
end
end end
end end
for k, v in pairs(items) do for item, v in pairs(items) do
inv:remove_item("main", fmt("%s %s", k, v * scrbar_val)) for _ = 1, v * scrbar_val do
end inv:remove_item("main", item)
end
if rcp_def.replacements then for _, pair in ipairs(rcp_def.replacements or {}) do
for _, pair in ipairs(rcp_def.replacements) do local old_name, new_name = unpack(pair)
get_stack(player, ItemStack(pair[2]))
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
end end
end end
@ -600,8 +610,8 @@ local function reset_data(data)
data.goto_page = nil data.goto_page = nil
data.recipes = nil data.recipes = nil
data.usages = nil data.usages = nil
data.crafting_rcp = nil data.crafting_rcp = nil
data.crafting_usg = nil data.crafting_usg = nil
data.alt_items = nil data.alt_items = nil
data.confirm_trash = nil data.confirm_trash = nil
data.show_settings = nil data.show_settings = nil
@ -613,13 +623,15 @@ local function reset_data(data)
end end
end end
local function add_hud_waypoint(player, name, pos, color) local function add_hud_waypoint(player, name, pos, color, image)
return player:hud_add { return player:hud_add {
hud_elem_type = "waypoint", hud_elem_type = image and "image_waypoint" or "waypoint",
name = name, name = name,
text = "m", text = image or "m",
scale = {x = 5, y = 5},
world_pos = pos, world_pos = pos,
number = color, number = color,
image = image,
z_index = -300, z_index = -300,
} }
end end

View File

@ -211,8 +211,6 @@ local function inv_fields(player, data, fields)
elseif fields.hide_debug_grid then elseif fields.hide_debug_grid then
data.hide_debug_grid = not data.hide_debug_grid data.hide_debug_grid = not data.hide_debug_grid
end end
return set_fs(player)
end end
local function select_item(player, data, fields) local function select_item(player, data, fields)
@ -465,11 +463,15 @@ core.register_on_player_receive_fields(function(player, formname, fields)
local tab = i3.tabs[data.tab] local tab = i3.tabs[data.tab]
if tab and tab.fields then if tab then
return true, tab.fields(player, data, fields) if tab.slots then
inv_fields(player, data, fields)
end
if tab.fields then
tab.fields(player, data, fields)
end
end end
return true, set_fs(player) return true, set_fs(player)
end) end)
return inv_fields

View File

@ -124,33 +124,13 @@ local function get_stack_max(inv, data, is_recipe, rcp)
end end
end end
return max_stacks local missing = {}
end
local function get_inv_slots(data, fs) for item, count in pairs(counts_rcp) do
local legacy_inventory = data.legacy_inventory missing[item] = max(0, count - (counts_inv[item] or 0))
local hotbar_len = data.hotbar_len
local inv_x = legacy_inventory and 0.23 or 0.22
local inv_y = legacy_inventory and 6.7 or 6.9
local spacing = legacy_inventory and 0.25 or 0.1
local size = 1
fs"style_type[box;colors=#77777710,#77777710,#777,#777]"
for i = 0, hotbar_len - 1 do
box(i * size + inv_x + (i * spacing), inv_y, size, size, "")
end end
fs("style_type[list;size=%f;spacing=%f]", size, spacing) return max_stacks, missing
fs("list[current_player;main;%f,%f;%u,1;]", inv_x, inv_y, hotbar_len)
fs("style_type[list;size=%f;spacing=%f,%f]", size, spacing, legacy_inventory and 0.15 or spacing)
fs("list[current_player;main;%f,%f;%u,%u;%u]", inv_x, inv_y + (legacy_inventory and 1.25 or 1.15),
hotbar_len, data.inv_size / hotbar_len, hotbar_len)
fs"style_type[list;size=1;spacing=0.15]"
fs"listring[current_player;craft]listring[current_player;main]"
end end
local function add_subtitle(fs, name, y, ctn_len, font_size, sep, label) local function add_subtitle(fs, name, y, ctn_len, font_size, sep, label)
@ -332,7 +312,7 @@ local function get_waypoint_fs(fs, data, player, yextra, ctn_len)
label(0.15, y + 0.33, clr(fmt("#%s", hex), waypoint_name)) label(0.15, y + 0.33, clr(fmt("#%s", hex), waypoint_name))
local tooltip = fmt("Name: %s\nPosition:%s", clr("#dbeeff", v.name), local tooltip = fmt("Name: %s\nPosition:%s", clr("#dbeeff", v.name),
v.pos:sub(2,-2):gsub("(%-*%d*%.?%d+)", clr("#dbeeff", " %1"))) v.pos:sub(2,-2):gsub("(%-*%d*%.?%d+)", clr("#dbeeff", " %1")))
if teleport_priv then if teleport_priv then
tooltip = fmt("%s\n%s", tooltip, clr("#ff0", ES"[Click to teleport]")) tooltip = fmt("%s\n%s", tooltip, clr("#ff0", ES"[Click to teleport]"))
@ -625,7 +605,9 @@ local function show_settings(fs, data)
end end
hypertext(2.2, 9.9, 6, 0.6, "home_pos", fmt("<global size=16><center>%s</center>", str)) hypertext(2.2, 9.9, 6, 0.6, "home_pos", fmt("<global size=16><center>%s</center>", str))
image_button(4.3, 10.4, 1.8, 0.7, "", "set_home", "Set home") fs("style[set_home;padding=20,10,-210,-10;fgimg=%s;fgimg_hovered=%s]", PNG.home_px, PNG.home_px_hover)
image_button(4.1, 10.4, 2.2, 0.7, "", "set_home", "")
label(4.9, 10.75, "Set home")
elseif show_style then elseif show_style then
checkbox(2.6, 9.95, "cb_hide_tabs", "Hide tabs", tostring(data.hide_tabs)) checkbox(2.6, 9.95, "cb_hide_tabs", "Hide tabs", tostring(data.hide_tabs))
@ -688,11 +670,53 @@ local function show_settings(fs, data)
end end
end end
local function get_footer(fs, data)
local btn = {
{"trash", ES"Clear inventory"},
{"sort", ES"Sort inventory"},
{"settings", ES"Settings"},
{"home", ES"Go home"},
}
for i, v in ipairs(btn) do
local btn_name, tooltip = unpack(v)
fs("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]",
btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)])
image_button(i + 3.43 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "")
fs("tooltip[%s;%s]", btn_name, tooltip)
end
show_settings(fs, data)
end
local function get_slots(fs, data)
local legacy_inventory = data.legacy_inventory
local hotbar_len = data.hotbar_len
local inv_x = legacy_inventory and 0.23 or 0.22
local inv_y = legacy_inventory and 6.7 or 6.9
local spacing = legacy_inventory and 0.25 or 0.1
local size = 1
fs"style_type[box;colors=#77777710,#77777710,#777,#777]"
for i = 0, hotbar_len - 1 do
box(i * size + inv_x + (i * spacing), inv_y, size, size, "")
end
fs("style_type[list;size=%f;spacing=%f]", size, spacing)
fs("list[current_player;main;%f,%f;%u,1;]", inv_x, inv_y, hotbar_len)
fs("style_type[list;size=%f;spacing=%f,%f]", size, spacing, legacy_inventory and 0.15 or spacing)
fs("list[current_player;main;%f,%f;%u,%u;%u]", inv_x, inv_y + (legacy_inventory and 1.25 or 1.15),
hotbar_len, data.inv_size / hotbar_len, hotbar_len)
fs"listring[current_player;craft]listring[current_player;main]"
get_footer(fs, data)
end
local function get_inventory_fs(player, data, fs) local function get_inventory_fs(player, data, fs)
fs"listcolors[#bababa50;#bababa99]"
get_inv_slots(data, fs)
local props = player:get_properties() local props = player:get_properties()
local ctn_len = 5.7 local ctn_len = 5.7
local ctn_hgt = data.legacy_inventory and 6.1 or 6.3 local ctn_hgt = data.legacy_inventory and 6.1 or 6.3
@ -772,23 +796,6 @@ local function get_inventory_fs(player, data, fs)
fs("scroll_container[3.9,0.2;%f,%f;scrbar_inv;vertical]", ctn_len, ctn_hgt) fs("scroll_container[3.9,0.2;%f,%f;scrbar_inv;vertical]", ctn_len, ctn_hgt)
get_container(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb, bag_size) get_container(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb, bag_size)
fs"scroll_container_end[]" fs"scroll_container_end[]"
local btn = {
{"trash", ES"Clear inventory"},
{"sort", ES"Sort inventory"},
{"settings", ES"Settings"},
{"home", ES"Go home"},
}
for i, v in ipairs(btn) do
local btn_name, tooltip = unpack(v)
fs("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]",
btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)])
image_button(i + 3.43 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "")
fs("tooltip[%s;%s]", btn_name, tooltip)
end
show_settings(fs, data)
end end
local function get_tooltip(item, info, lang_code) local function get_tooltip(item, info, lang_code)
@ -946,6 +953,12 @@ local function get_output_fs(fs, data, rcp, is_recipe, is_usage, shapeless, righ
local itemstr = ESC(item:to_string()) local itemstr = ESC(item:to_string())
item_image_button(X + 0.11, Y, BTN_SIZE, BTN_SIZE, itemstr, _name, "") item_image_button(X + 0.11, Y, BTN_SIZE, BTN_SIZE, itemstr, _name, "")
local stackmax = item:get_stack_max()
if stackmax == 1 and count > 1 then
label(X + 1.05, Y + 1, count)
end
local def = reg_items[name] local def = reg_items[name]
local unknown = not def or nil local unknown = not def or nil
local desc = def and def.description local desc = def and def.description
@ -1044,11 +1057,9 @@ local function get_grid_fs(fs, data, rcp, is_recipe, is_usage)
local label = groups and "\nG" or "" local label = groups and "\nG" or ""
local replace local replace
for j = 1, #(rcp.replacements or {}) do for _, replacement in ipairs(rcp.replacements or {}) do
local replacement = rcp.replacements[j]
if replacement[1] == name then if replacement[1] == name then
replace = replace or {type = rcp.type, items = {}} replace = replace or {type = rcp.type, items = {}}
local added local added
for _, v in ipairs(replace.items) do for _, v in ipairs(replace.items) do
@ -1059,12 +1070,17 @@ local function get_grid_fs(fs, data, rcp, is_recipe, is_usage)
end end
if not added then if not added then
label = fmt("%s%s\nR", label ~= "" and "\n" or "", label) label = fmt("%s\nR", label)
insert(replace.items, replacement[2]) insert(replace.items, replacement[2])
end end
end end
end end
local _, count_sub = label:gsub("\n", "")
if count_sub == 2 then
label = label:sub(2)
end
if not large_recipe then if not large_recipe then
slot(X, Y, btn_size, btn_size) slot(X, Y, btn_size, btn_size)
end end
@ -1087,7 +1103,14 @@ local function get_grid_fs(fs, data, rcp, is_recipe, is_usage)
item:set_name(name) item:set_name(name)
item:set_count(count) item:set_count(count)
local itemstr = ESC(item:to_string()) local itemstr = ESC(item:to_string())
item_image_button(X, Y, btn_size, btn_size, itemstr, btn_name, label) item_image_button(X, Y, btn_size, btn_size, itemstr, btn_name, label)
local stackmax = item:get_stack_max()
if stackmax == 1 and count > 1 then
label(X + 0.95, Y + 0.95, count)
end
end end
local def = reg_items[name] local def = reg_items[name]
@ -1298,9 +1321,10 @@ local function get_crafting_fs(fs, data, is_recipe, is_usage, max_stacks_rcp, ma
fs("scrollbaroptions[min=1;max=%u;smallstep=1]", craft_max) fs("scrollbaroptions[min=1;max=%u;smallstep=1]", craft_max)
scrollbar(x + 0.2, data.yoffset + 1.45, 2.5, 0.35, "horizontal", fmt("scrbar_%s", name), stack_fs) scrollbar(x + 0.2, data.yoffset + 1.45, 2.5, 0.35, "horizontal", fmt("scrbar_%s", name), stack_fs)
button(x + 0.2, data.yoffset + 1.85, 2.5, 0.7, fmt("craft_%s", name), ES("Craft (×@1)", stack_fs)) button(x + 0.2, data.yoffset + 1.9, 2.5, 0.7, fmt("craft_%s", name), ES("Craft (×@1)", stack_fs))
fs"style_type[label;font_size=16;textcolor=#fff]" fs"style_type[label;font_size=16;textcolor=#fff]"
fs"style_type[image,button,image_button;noclip=false]"
end end
local function get_rcp_extra(fs, data, player, panel, is_recipe, is_usage) local function get_rcp_extra(fs, data, player, panel, is_recipe, is_usage)
@ -1310,15 +1334,15 @@ local function get_rcp_extra(fs, data, player, panel, is_recipe, is_usage)
if rn then if rn then
local rcp_ok = is_recipe and panel.rcp[data.rnum].type == "normal" local rcp_ok = is_recipe and panel.rcp[data.rnum].type == "normal"
local usg_ok = is_usage and panel.rcp[data.unum].type == "normal" local usg_ok = is_usage and panel.rcp[data.unum].type == "normal"
local max_stacks_rcp, max_stacks_usg = 0, 0 local max_stacks_rcp, max_stacks_usg, missing_rcp, missing_usg = 0, 0
local inv = player:get_inventory() local inv = player:get_inventory()
if rcp_ok then if rcp_ok then
max_stacks_rcp = get_stack_max(inv, data, is_recipe, panel.rcp[data.rnum]) max_stacks_rcp, missing_rcp = get_stack_max(inv, data, is_recipe, panel.rcp[data.rnum])
end end
if usg_ok then if usg_ok then
max_stacks_usg = get_stack_max(inv, data, is_recipe, panel.rcp[data.unum]) max_stacks_usg, missing_usg = get_stack_max(inv, data, is_recipe, panel.rcp[data.unum])
end end
if is_recipe and max_stacks_rcp == 0 then if is_recipe and max_stacks_rcp == 0 then
@ -1331,6 +1355,23 @@ local function get_rcp_extra(fs, data, player, panel, is_recipe, is_usage)
if max_stacks_rcp > 0 or max_stacks_usg > 0 then if max_stacks_rcp > 0 or max_stacks_usg > 0 then
get_crafting_fs(fs, data, is_recipe, is_usage, max_stacks_rcp, max_stacks_usg) get_crafting_fs(fs, data, is_recipe, is_usage, max_stacks_rcp, max_stacks_usg)
elseif rcp_ok or usg_ok then
local X = data.inv_width + 7.35
local Y = data.yoffset + 0.2
local missing = is_recipe and missing_rcp or missing_usg
local str = ""
for item, count in pairs(missing) do
if count > 0 then
local name = is_group(item) and (i3.group_names[item:sub(7)] or item) or
get_desc(item, data.lang_code)
str = fmt("%s\n%s %s", str, clr("#ff0", count .. '×'), name)
end
end
image(X, Y, 0.45, 0.45, PNG.crafting .. "^\\[opacity:100")
tooltip(X, Y, 0.45, 0.45, fmt("%s:%s", ES"Missing materials for crafting", str))
end end
get_rcp_lbl(fs, data, panel, rn, is_recipe, is_usage) get_rcp_lbl(fs, data, panel, rn, is_recipe, is_usage)
@ -1391,7 +1432,20 @@ local function get_header_items_fs(fs, data)
end end
box(X + 1, 0.2, 0.01, 0.5, "#bababa50") box(X + 1, 0.2, 0.01, 0.5, "#bababa50")
local cat = {{"all", "all items"}, {"node", "nodes only"}, {"item", "items only"}}
local cat = {{"all", "all items"}}
if not recipe_filter_set() then
for _, v in ipairs(i3.minitabs) do
if v.name == "nodes" then
insert(cat, {"node", "nodes only"})
end
if v.name == "items" then
insert(cat, {"item", "items only"})
end
end
end
for i in ipairs(cat) do for i in ipairs(cat) do
local name, desc = unpack(cat[i]) local name, desc = unpack(cat[i])
@ -1452,22 +1506,36 @@ local function get_header_items_fs(fs, data)
end end
end end
local function get_minitabs(fs, data, full_height) local function get_minitabs(fs, data, player, full_height)
local _tabs = {"All", "Nodes", "Items"} local minitabs = {}
local tab_len, tab_hgh = 1.8, 0.5
for i, title in ipairs(_tabs) do for i, v in ipairs(i3.minitabs) do
local selected = i == data.itab local access = v.access
if access == nil or access(player, data) then
minitabs[i] = v.description
end
end
local tab_len, tab_hgh, i = 1.8, 0.5, 0
for id, title in pairs(minitabs) do
i++
local top = i > 3
local X = top and i - 3 or i
local selected = id == data.itab
local hover_texture = selected and PNG.tab_small_hover or PNG.tab_small local hover_texture = selected and PNG.tab_small_hover or PNG.tab_small
local flip = top and "^[transformFY" or ""
local tabname = fmt("itab_%u", id)
fs([[ style_type[image_button;bgimg=%s;bgimg_hovered=%s; fs([[ style[%s;bgimg=%s%s;bgimg_hovered=%s%s;noclip=true;font=bold;font_size=16;
bgimg_middle=14,0,-14,-14;padding=-14,0,14,14] ]], hover_texture, PNG.tab_small_hover) textcolor=%s;content_offset=0;sound=i3_tab;bgimg_middle=14,0,-14,-14;padding=-14,0,14,14] ]],
tabname, hover_texture, flip, PNG.tab_small_hover, flip, selected and "#fff" or "#bbb")
fs([[ style_type[image_button;noclip=true;font=bold;font_size=16; fs("style[%s:hovered;textcolor=#fff]", tabname)
textcolor=%s;content_offset=0;sound=i3_tab] ]], selected and "#fff" or "#bbb")
fs"style_type[image_button:hovered;textcolor=#fff]" image_button((data.inv_width - 0.65) + (X * (tab_len + 0.1)),
image_button((data.inv_width - 0.65) + (i * (tab_len + 0.1)), top and -tab_hgh or full_height, tab_len, tab_hgh, "", tabname, title)
full_height, tab_len, tab_hgh, "", fmt("itab_%u", i), title)
end end
end end
@ -1515,7 +1583,7 @@ local function get_items_fs(fs, data, player, full_height)
local item_btn = fmt("item_image_button", X, Y, size, size, name, item, "") local item_btn = fmt("item_image_button", X, Y, size, size, name, item, "")
if recipe_filter_set() then if recipe_filter_set() and data.itab == 1 then
if data.items_progress[item] then if data.items_progress[item] then
insert(fs, item_btn) insert(fs, item_btn)
else else
@ -1616,17 +1684,17 @@ local function get_tabs_fs(fs, player, data, full_height)
local middle = btm and "16,0,-16,-16" or "16,16,-16,-16" local middle = btm and "16,0,-16,-16" or "16,16,-16,-16"
local padding = btm and "-16,0,16,16" or "-16,-16,16,16" local padding = btm and "-16,0,16,16" or "-16,-16,16,16"
fs([[ style_type[image_button;bgimg=%s;bgimg_hovered=%s;bgimg_middle=%s;padding=%s] ]], local tabname = fmt("tab_%s", def.name)
bgimg, bgimg_hover, middle, padding)
fs("style_type[image_button;noclip=true;font_size=16;textcolor=%s;content_offset=0;sound=i3_tab]", fs([[ style[%s;bgimg=%s;bgimg_hovered=%s;bgimg_middle=%s;padding=%s;noclip=true;
selected and "#fff" or "#ddd") font=bold;font_size=16;textcolor=%s;content_offset=0;sound=i3_tab] ]],
tabname, bgimg, bgimg_hover, middle, padding, selected and "#fff" or "#bbb")
local X = (data.inv_width / 2) + (c * (tab_len + 0.1)) - ((tab_len + 0.05) * (shift / 2)) local X = (data.inv_width / 2) + (c * (tab_len + 0.1)) - ((tab_len + 0.05) * (shift / 2))
local Y = btm and full_height or -tab_hgh local Y = btm and full_height or -tab_hgh
fs"style_type[image_button:hovered;textcolor=#fff]" fs("style[%s:hovered;textcolor=#fff]", tabname)
image_button(X, Y, tab_len, tab_hgh, "", fmt("tab_%s", def.name), ESC(def.description)) image_button(X, Y, tab_len, tab_hgh, "", tabname, ESC(def.description))
if true_str(def.image) then if true_str(def.image) then
local desc = translate(data.lang_code, def.description) local desc = translate(data.lang_code, def.description)
@ -1634,6 +1702,7 @@ local function get_tabs_fs(fs, player, data, full_height)
fs"style_type[image;noclip=true]" fs"style_type[image;noclip=true]"
image(X + (tab_len / 2) - ((desc_len * 0.1) / 2) - 0.55, Y + 0.05, 0.35, 0.35, def.image) image(X + (tab_len / 2) - ((desc_len * 0.1) / 2) - 0.55, Y + 0.05, 0.35, 0.35, def.image)
fs"style_type[image;noclip=false]"
end end
c++ c++
@ -1696,8 +1765,14 @@ local function make_fs(player, data)
local tab = i3.tabs[data.tab] local tab = i3.tabs[data.tab]
if tab and tab.formspec then if tab then
tab.formspec(player, data, fs) if tab.formspec then
tab.formspec(player, data, fs)
end
if tab.slots then
get_slots(fs, data)
end
end end
if data.query_item then if data.query_item then
@ -1706,7 +1781,7 @@ local function make_fs(player, data)
get_items_fs(fs, data, player, full_height) get_items_fs(fs, data, player, full_height)
if not data.hide_tabs then if not data.hide_tabs then
get_minitabs(fs, data, full_height) get_minitabs(fs, data, player, full_height)
end end
end end

View File

@ -15,16 +15,18 @@ local function init_hud(player)
data.hud = { data.hud = {
bg = player:hud_add { bg = player:hud_add {
hud_elem_type = "image", hud_elem_type = "image",
position = {x = 0.78, y = 1}, position = {x = 1, y = 1},
offset = {x = -320, y = 0},
alignment = {x = 1, y = 1}, alignment = {x = 1, y = 1},
scale = {x = 370, y = 112}, scale = {x = 300, y = 105},
text = "i3_bg.png", text = "i3_bg.png",
z_index = 0xDEAD, z_index = 0xDEAD,
}, },
img = player:hud_add { img = player:hud_add {
hud_elem_type = "image", hud_elem_type = "image",
position = {x = 0.79, y = 1.02}, position = {x = 1, y = 1},
offset = {x = -310, y = 20},
alignment = {x = 1, y = 1}, alignment = {x = 1, y = 1},
scale = {x = 1, y = 1}, scale = {x = 1, y = 1},
text = "", text = "",
@ -33,7 +35,8 @@ local function init_hud(player)
text = player:hud_add { text = player:hud_add {
hud_elem_type = "text", hud_elem_type = "text",
position = {x = 0.84, y = 1.04}, position = {x = 1, y = 1},
offset = {x = -235, y = 40},
alignment = {x = 1, y = 1}, alignment = {x = 1, y = 1},
number = 0xffffff, number = 0xffffff,
text = "", text = "",
@ -55,13 +58,12 @@ local function init_hud(player)
end end
local function show_hud(player, data) 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 hud_info_bg = player:hud_get(data.hud.bg) local hud_info_bg = player:hud_get(data.hud.bg)
local dt = 0.025 local dt = 0.016
local offset_y = hud_info_bg.offset.y
local speed = 5 * i3.settings.hud_speed
if hud_info_bg.position.y <= 0.9 then if offset_y < -100 then
data.show_hud = false data.show_hud = false
data.hud_timer = (data.hud_timer or 0) + dt data.hud_timer = (data.hud_timer or 0) + dt
end end
@ -73,31 +75,34 @@ local function show_hud(player, data)
end end
if data.show_hud then if data.show_hud then
for _, def in pairs(data.hud) do for name, def in pairs(data.hud) do
local hud_info = player:hud_get(def) if name ~= "wielditem" then
player:hud_change(def, "position", {
x = hud_info.position.x,
y = hud_info.position.y - ((dt / 5) * i3.settings.hud_speed)
})
end
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) local hud_info = player:hud_get(def)
player:hud_change(def, "position", { player:hud_change(def, "offset", {
x = hud_info.position.x, x = hud_info.offset.x,
y = hud_info.position.y + ((dt / 5) * i3.settings.hud_speed) y = hud_info.offset.y - speed
}) })
end end
end
elseif data.show_hud == false then
if data.hud_timer >= i3.settings.hud_timer_max then
for name, def in pairs(data.hud) do
if name ~= "wielditem" then
local hud_info = player:hud_get(def)
if hud_info_bg.position.y >= 1 then player:hud_change(def, "offset", {
data.show_hud = nil x = hud_info.offset.x,
y = hud_info.offset.y + speed
})
end
end
if offset_y > 0 then
data.show_hud = nil
data.hud_timer = nil data.hud_timer = nil
data.hud_msg = nil data.hud_msg = nil
data.hud_img = nil data.hud_img = nil
end end
end end
end end
@ -113,8 +118,12 @@ core.register_globalstep(function(dt)
local data = i3.data[name] local data = i3.data[name]
if not data then return end if not data then return end
local has_text = player:hud_get(data.hud.wielditem).text ~= ""
if not data.wielditem_hud then if not data.wielditem_hud then
player:hud_change(data.hud.wielditem, "text", "") if has_text then
player:hud_change(data.hud.wielditem, "text", "")
end
return return
end end
@ -122,7 +131,7 @@ core.register_globalstep(function(dt)
local wieldidx = player:get_wield_index() local wieldidx = player:get_wield_index()
if wieldidx == data.old_wieldidx then if wieldidx == data.old_wieldidx then
if data.timer >= i3.settings.wielditem_fade_after then if data.timer >= i3.settings.wielditem_fade_after and has_text then
player:hud_change(data.hud.wielditem, "text", "") player:hud_change(data.hud.wielditem, "text", "")
end end
return return
@ -165,7 +174,7 @@ local function init_waypoints(player)
for _, v in ipairs(data.waypoints) do for _, v in ipairs(data.waypoints) do
if not v.hide then if not v.hide then
local id = add_hud_waypoint(player, v.name, str_to_pos(v.pos), v.color) local id = add_hud_waypoint(player, v.name, str_to_pos(v.pos), v.color, v.image)
v.id = id v.id = id
end end
end end

View File

@ -2,9 +2,19 @@ local set_fs = i3.set_fs
local hud_notif = i3.hud_notif local hud_notif = i3.hud_notif
local POLL_FREQ = 0.25 local POLL_FREQ = 0.25
IMPORT("reg_items", "reg_nodes") IMPORT("reg_items", "reg_nodes", "fmt", "table_merge", "array_diff")
IMPORT("fmt", "search", "table_merge", "array_diff") IMPORT("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters", "sort_by_category")
IMPORT("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters")
i3.remove_minitab"nodes"
i3.remove_minitab"items"
i3.new_minitab("unlocked", {
description = "Unlocked",
sorter = function(item, data)
return data.items_progress[item]
end
})
local function get_filtered_items(player, data) local function get_filtered_items(player, data)
local items, known = {}, 0 local items, known = {}, 0
@ -145,9 +155,8 @@ local function poll_new_items(player, data, join)
end end
data.items_progress = items data.items_progress = items
data.itab = 1
search(data) sort_by_category(data)
set_fs(player) set_fs(player)
end end

View File

@ -54,6 +54,8 @@ local PNG = {
node = "i3_node.png", node = "i3_node.png",
item = "i3_item.png", item = "i3_item.png",
cube = "i3_cube.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", cancel_hover = "i3_cancel.png^\\[brighten",
search_hover = "i3_search.png^\\[brighten", search_hover = "i3_search.png^\\[brighten",
@ -83,6 +85,9 @@ local PNG = {
} }
local styles = string.format([[ local styles = string.format([[
listcolors[#bababa50;#bababa99]
style_type[list;size=1;spacing=0.15]
style_type[field;border=false;bgcolor=transparent] style_type[field;border=false;bgcolor=transparent]
style_type[label,field;font_size=16] style_type[label,field;font_size=16]
style_type[button;border=false;content_offset=0] style_type[button;border=false;content_offset=0]

View File

@ -1,16 +1,32 @@
local SWITCH
i3.new_tab("test1", { i3.new_tab("test1", {
description = "Test 1 Test 1", description = "Test 1 Test 1",
image = "i3_heart.png", image = "i3_heart.png",
formspec = function(player, data, fs) formspec = function(player, data, fs)
fs("button", 3, 4, 3, 0.8, "test", "Click here")
fs("label", 3, 1, "Just a test") fs("label", 3, 1, "Just a test")
fs"label[3,2;Lorem Ipsum]"
if SWITCH then
fs"label[3,2;Button clicked]"
else
fs"label[3,2;Lorem Ipsum]"
end
end, end,
fields = function(player, data, fields)
if fields.test then
SWITCH = true
end
end
}) })
i3.new_tab("test2", { i3.new_tab("test2", {
description = "Test 2", description = "Test 2",
image = "i3_mesepick.png", image = "i3_mesepick.png",
slots = true,
formspec = function(player, data, fs) formspec = function(player, data, fs)
fs("label[3,1;Test 2]") fs("label[3,1;Test 2]")

12
tests/test_waypoints.lua Normal file
View File

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

BIN
textures/i3_home_px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB