Released v1.0.0

Wonderful news for many, the issue of storage is over \o/

  Enjoy a simple yet powerful chest mod with a very power tool too!
This commit is contained in:
Apollo 2022-01-30 15:10:17 -05:00
commit 7fea6f0fb4
15 changed files with 633 additions and 0 deletions

20
.luacheckrc Normal file
View File

@ -0,0 +1,20 @@
unused_args = false
allow_defined_top = true
globals = {
"minetest",
}
read_globals = {
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
-- Builtin
"vector", "ItemStack",
"dump", "DIR_DELIM", "VoxelArea", "Settings",
-- MTG
"default", "sfinv", "creative",
}

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# Chest2
The Advanced Chest mod
## What's in the box
- A chest (single node, but unlike all other chests, somewhat larger inventory size, but gains paging, so you get multiple inventories in 1 chest)
- Simple to use features
- Settings by `minetest.conf`
- A remote (Access your Chest2 chests via remote)
## How the `chest2:chest` differs from say `default:chest`?
While a standard chest requires you to make keys for others to access it too, Chest2 simply requires the `Members` setting and 1 player name per line (that's `bob123` or `tv_frank` but one on each line, then click Save)
While a standard chest is 8x3 the Chest2 is 12x5 not to mention Chest2 supports multiple pages (which are seperate inventories, so 12x5 times each page)
While there is no way to remotely access a standard chest, Chest2 comes with a Remote, just punch a `chest2:chest` with the remote to connect it to that Chest, then from there punch (left click), use (right click) to access that chest from anywhere in the world (unlimited distance included).
> Note, the remote will reset to a unconnected remote if it looses connection/i.e. someone broke the chest/moved the chest. (When the mod refers to chest it refers to `chest2:chest`)
## Settings
This mod comes with almost full customization from crafting a remote, to crafting a `chest2:chest`, even the number of pages in a `chest2:chest`.
Just run this mod, (it auto-generates the settings into your `minetest.conf`),
then edit `minetest.conf` or for singleplayer worlds change it via Minetest Settings (under the Mods section will be Chest2)

34
crafting.lua Normal file
View File

@ -0,0 +1,34 @@
local empty = ""
local gold_block = ""
local diamond_block = ""
if chest2.GAMEMODE == "MTG" then
gold_block = "default:gold_block"
diamond_block = "default:diamond_block"
elseif chest2.GAMEMODE == "MCL2" or chest2.GAMEMODE == "MCL5" then
gold_block = "mcl_core:gold_block"
diamond_block = "mcl_core:diamond_block"
end
if chest2.settings.craft_chest then
minetest.register_craft({
output = "chest2:chest 1",
recipe = {
{diamond_block, gold_block, diamond_block},
{gold_block, empty, gold_block},
{diamond_block, gold_block, diamond_block}
}
})
end
if chest2.settings.craft_remote then
minetest.register_craft({
output = "chest2:remote_off 1",
recipe = {
{empty, gold_block, empty},
{gold_block, diamond_block, gold_block},
{gold_block, diamond_block, gold_block}
}
})
end

37
init.lua Normal file
View File

@ -0,0 +1,37 @@
--[[
Chest2
A multi page chest node.
Also comes with easy gui for adding users or removing users
from and to the chest node.
Additional in 2022:
- Make a remote which will allow accessing a chest from anywhere
]]
-- Public API (Also used by some internals)
chest2 = {}
chest2.S = minetest.get_translator("chest2")
chest2.modpath = minetest.get_modpath("chest2")
chest2.VERSION = "1.0.0"
if minetest.registered_nodes["default:stone"] then
chest2.GAMEMODE = "MTG"
elseif minetest.registered_nodes["mcl_deepslate:deepslate"] then
chest2.GAMEMODE = "MCL5"
elseif minetest.registered_nodes["mcl_core:stone"] then
chest2.GAMEMODE = "MCL2"
else
chest2.GAMEMODE = "???"
end
dofile(chest2.modpath.."/settings.lua") -- Settings
dofile(chest2.modpath.."/tool_belt.lua") -- Utility functions
dofile(chest2.modpath.."/register.lua") -- The Chest2
chest2.tools.log("Version: "..chest2.VERSION)
chest2.tools.log("Gamemode: "..chest2.GAMEMODE)

5
mod.conf Normal file
View File

@ -0,0 +1,5 @@
name = chest2
description = The Advanced Chest mod
author = ApolloX
optional_depends = default, mcl_core, mcl_sounds, mcl_formspec, mcl_deepslate

403
register.lua Normal file
View File

@ -0,0 +1,403 @@
chest2.remotes = {}
local function get_context(name)
local context = chest2.remotes[name] or {}
chest2.remotes[name] = context
return context
end
minetest.register_on_leaveplayer(function(player)
chest2.remotes[player:get_player_name()] = nil
end)
chest2.showform = function(pos,player)
local meta = minetest.get_meta(pos)
local uname = player:get_player_name()
if meta:get_string("owner")~=uname and not minetest.check_player_privs(uname, {protection_bypass=true}) then
local users = chest2.tools.split(meta:get_string("names"), "\n")
local found = false
for x=1, #users, 1 do
if users[x] == player then
found = true
break
end
end
if found ~= true then
chest2.tools.log("Chest2:chest blocked '"..uname.."' from accessing chest at "..minetest.pos_to_string(pos))
return
end
end
--local inv = meta:get_inventory()
local inv = minetest.get_inventory({ type="node", pos=pos })
local spos=pos.x .. "," .. pos.y .. "," .. pos.z
local title = meta:get_string("title")
local names = meta:get_string("names")
local page = meta:get_int("page")
local op = meta:get_int("open")
local open = ""
local mclform = rawget(_G, "mcl_formspec") or nil
local gui=""
gui=gui.."size[16,10]"
if op==0 then
open="Only U"
elseif op==1 then
open="Members"
else
open="Public"
end
if title=="" then
title="Chest2"
end
gui=gui .. ""
.."button[12,1; 1.9,1;save;Save]"
.."button[12,2; 1.9,1;open;" .. open .."]"
.."label[12, 3;Members List]"
.."label[12, 3.4;(Inventory Access)]"
.."textarea[12.2,4;4,5;names;;" .. names .."]"
.."button[-0.2,7;1,1;prev;<]"
.."button[0.95,7;1,1;next;>]"
.."label[0.65,7.2;".. minetest.formspec_escape(tostring(page)) .. "]"
--chest2.tools.log("main"..tostring(page).." ("..tostring(inv:get_size("main"..tostring(page)))..", "..minetest.serialize(inv:get_location())..")")
if chest2.GAMEMODE == "MCL2" or chest2.GAMEMODE == "MCL5" or chest2.GAMEMODE == "MCL" then
gui=gui..""
.."list[nodemeta:" .. spos .. ";main"..tostring(page)..";0,0;12,5;]"
--.."list[context;main"..tostring(page)..";0,0;12,5]"
..mclform.get_itemslot_bg(0, 0, 12, 5)
.."list[current_player;main;2,6;9,3;]"
..mclform.get_itemslot_bg(2, 6, 9, 3)
else
gui=gui..""
.."list[nodemeta:" .. spos .. ";main"..tostring(page)..";0,0;12,5;]"
--.."list[context;main"..tostring(page)..";0,0;12,5]"
.."list[current_player;main;2,6;8,4;]"
end
gui=gui .. ""
.."listring[nodemeta:" .. spos .. ";main"..tostring(page).."]"
--.."listring[current_player;main"..tostring(page).."]"
meta:set_string("formspec", gui)
return true
end
minetest.register_node("chest2:chest", {
description = "Chest2",
tiles = {
"chest2_plate.png", -- top
"chest2_plate.png", -- bottom
"chest2_side1.png", -- right
"chest2_side0.png", -- left
"chest2_plate.png", -- back
"chest2_front.png", -- front
},
groups = {choppy = 2, oddly_breakable_by_hand = 1,tubedevice = 1, tubedevice_receiver = 1,mesecon=2, handy=1,axey=1},
paramtype2 = "facedir",
sunlight_propagates = true,
light_source = 3,
tube = {insert_object = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local added = stack
for x=1, chest2.settings.pages, 1 do
if inv:room_for_item("main"..tostring(x)) then
added = inv:add_item("main"..tostring(x), stack)
break
end
end
return added
end,
can_insert = function(pos, node, stack, direction)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local valid = false
for x=1, chest2.settings.pages, 1 do
if inv:room_for_item("main"..tostring(x), stack) then
valid = true
break
end
end
return valid
end,
--input_inventory = "main", -- Invalid, won't work since we use paging
connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1}},
after_place_node = function(pos, placer)
local meta=minetest.get_meta(pos)
local name=placer:get_player_name()
meta:set_string("owner",name)
meta:set_string("infotext", "Chest2 (" .. name .. ")")
chest2.showform(pos, placer) -- Actually this makes the formspec meta field
end,
on_construct = function(pos)
local meta=minetest.get_meta(pos)
meta:set_int("page", 1)
meta:set_int("max_page", chest2.settings.pages)
meta:set_string("names", "")
meta:set_int("open", 0)
--meta:get_inventory():set_size("main", 60)
local inv = meta:get_inventory()
for x=1, meta:get_int("max_page"), 1 do
inv:set_size("main"..tostring(x), 60)
chest2.tools.log("Inventory: main"..tostring(x).." with 60")
end
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if (minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true})) then
return stack:get_count()
end
local meta = minetest.get_meta(pos)
if meta:get_int("open") == 1 then
local users = chest2.tools.split(meta:get_string("names"), "\n")
for x=1, #users, 1 do
if users[x] == player then
return stack:get_count()
end
end
elseif meta:get_int("open") == 2 then
return stack:get_count()
end
chest2.tools.log("chest2:chest blocked player '"..player:get_player_name().."' from putting "..stack:get_name().." x "..tostring(stack:get_count()))
return 0
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then
return stack:get_count()
end
if meta:get_int("open") == 1 then
local users = chest2.tools.split(meta:get_string("names"), "\n")
for x=1, #users, 1 do
if users[x] == player then
return stack:get_count()
end
end
elseif meta:get_int("open") == 2 then
return stack:get_count()
end
chest2.tools.log("chest2:chest blocked player '"..player:get_player_name().."' from taking "..stack:get_name().." x "..tostring(stack:get_count()))
return 0
end,
can_dig = function(pos, player)
local meta=minetest.get_meta(pos)
local inv=meta:get_inventory()
local p=player:get_player_name()
local empty = true
for x=1, meta:get_int("max_page"), 1 do
if not inv:is_empty("main"..tostring(x)) then
empty = false
break
end
end
if (meta:get_string("owner")==p or minetest.check_player_privs(p, {protection_bypass=true})) and empty or meta:get_string("owner")=="" then
return true
end
end,
on_receive_fields = function(pos, formname, fields, sender)
--chest2.tools.log("'"..formname.."'")
local meta = minetest.get_meta(pos)
if fields.next then
--chest2.tools.log("Next Page")
local page = meta:get_int("page")
page=page+1
if page > meta:get_int("max_page") then page = 1 end
meta:set_int("page", page)
chest2.showform(pos, sender)
end
if fields.prev then
--chest2.tools.log("Previous Page")
local page = meta:get_int("page")
page=page-1
if page < 1 then page = meta:get_int("max_page") end
meta:set_int("page", page)
chest2.showform(pos, sender)
end
if sender:get_player_name() ~= meta:get_string("owner") then
return
end
if fields.save then
--chest2.tools.log("Save Names")
meta:set_string("names", fields.names)
chest2.showform(pos, sender)
end
if fields.open then
--chest2.tools.log("Open Access")
local open=meta:get_int("open")
open=open+1
if open>2 then open=0 end
meta:set_int("open",open)
chest2.showform(pos, sender)
end
end,
})
chest2.connect_remote = function (itemstack, user, pointed_thing)
if pointed_thing ~= nil then
if pointed_thing.under ~= nil then
local node = minetest.get_node_or_nil(pointed_thing.under)
if node.name == "chest2:chest" then
local connected = ItemStack("chest2:remote_on")
local con_meta = connected:get_meta()
con_meta:set_string("connection", chest2.tools.pos2str(pointed_thing.under))
local meta_dat = get_context(user:get_player_name())
meta_dat.pos = pointed_thing.under
minetest.chat_send_player(user:get_player_name(), "Connected to "..minetest.pos_to_string(pointed_thing.under))
return connected
end
end
end
end
minetest.register_craftitem("chest2:remote_off", {
short_description = "Remote (Disconnected)",
description = "Remote (Disconnected)\nPunch a chest to connect",
inventory_image = "chest2_remote_off.png",
stack_max = 1,
on_use = chest2.connect_remote,
on_place = chest2.connect_remote,
on_secondary_use = chest2.connect_remote,
})
chest2.use_remote = function (itemstack, user, pointed_thing)
local meta = itemstack:get_meta()
local pointed_chest = false
-- Perform reconnect or attempt to access node
if pointed_thing ~= nil then -- Perform reconnect method
if pointed_thing.under ~= nil then
local node = minetest.get_node_or_nil(pointed_thing.under)
if node.name == "chest2:chest" then
local con_meta = itemstack:get_meta()
con_meta:set_string("connection", chest2.tools.pos2str(pointed_thing.under))
minetest.chat_send_player(user:get_player_name(), "Connected to "..minetest.pos_to_string(pointed_thing.under))
local meta_dat = get_context(user:get_player_name())
meta_dat.pos = pointed_thing.under
pointed_chest = true
return itemstack
end
end
end
if not pointed_chest then
-- Check connection
if meta:get_string("connection") ~= "" or meta:get_string("connection") ~= nil then
local pos = chest2.tools.str2pos(meta:get_string("connection"))
local valid = false
if pos ~= nil then
-- Ensure the connection is valid
if chest2.vm ~= nil then
chest2.vm:read_from_map(pos, pos)
local node = minetest.get_node_or_nil(pos)
if node ~= nil then
if node.name == "chest2:chest" then
valid = true
end
end
else
chest2.vm = minetest.get_voxel_manip()
chest2.vm:read_from_map(pos, pos)
local node = minetest.get_node_or_nil(pos)
if node ~= nil then
if node.name == "chest2:chest" then
valid = true
end
end
end
-- If the connection is valid then attempt to show the formspec for that location
if valid == true then
local rc = chest2.showform(pos, user)
if rc == true then
local nmeta = minetest.get_meta(pos)
local meta_dat = get_context(user:get_player_name())
meta_dat.pos = pos
minetest.show_formspec(user:get_player_name(), "chest2:remote_form", nmeta:get_string("formspec"))
else
minetest.chat_send_player(user:get_player_name(), "Connection "..minetest.pos_to_string(pos).." reported access denied")
end
else
-- Reset the item back to off
local dis = ItemStack("chest2:remote_off")
minetest.chat_send_player(user:get_player_name(), "Disconnected from "..minetest.pos_to_string(pos))
local meta_dat = get_context(user:get_player_name())
meta_dat.pos = nil
return dis
end
else
-- Reset the item back to off
local dis = ItemStack("chest2:remote_off")
minetest.chat_send_player(user:get_player_name(), "Network Error Disconnected")
local meta_dat = get_context(user:get_player_name())
meta_dat.pos = nil
return dis
end
end
end
end
minetest.register_craftitem("chest2:remote_on", {
short_description = "Remote (Connected)",
description = "Remote (Connected)\nPunch anywhere to open\nPunch a chest to reconnect",
inventory_image = "chest2_remote_on.png",
stack_max = 1,
on_use = chest2.use_remote,
on_place = chest2.use_remote,
on_secondary_use = chest2.use_remote
})
minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname ~= "chest2:remote_form" then
return
end
local meta_dat = get_context(player:get_player_name())
if meta_dat == nil or meta_dat.pos == nil then
return
end
local meta = minetest.get_meta(meta_dat.pos)
if fields.next then
--chest2.tools.log("Next Page")
local page = meta:get_int("page")
page=page+1
if page > meta:get_int("max_page") then page = 1 end
meta:set_int("page", page)
chest2.showform(meta_dat.pos, player)
minetest.show_formspec(player:get_player_name(), "chest2:remote_form", meta:get_string("formspec"))
end
if fields.prev then
--chest2.tools.log("Previous Page")
local page = meta:get_int("page")
page=page-1
if page < 1 then page = meta:get_int("max_page") end
meta:set_int("page", page)
chest2.showform(meta_dat.pos, player)
minetest.show_formspec(player:get_player_name(), "chest2:remote_form", meta:get_string("formspec"))
end
if player:get_player_name() ~= meta:get_string("owner") then
return
end
if fields.save then
--chest2.tools.log("Save Names")
meta:set_string("names", fields.names)
chest2.showform(meta_dat.pos, player)
minetest.show_formspec(player:get_player_name(), "chest2:remote_form", meta:get_string("formspec"))
end
if fields.open then
--chest2.tools.log("Open Access")
local open=meta:get_int("open")
open=open+1
if open>2 then open=0 end
meta:set_int("open",open)
chest2.showform(meta_dat.pos, player)
minetest.show_formspec(player:get_player_name(), "chest2:remote_form", meta:get_string("formspec"))
end
end)

26
settings.lua Normal file
View File

@ -0,0 +1,26 @@
-- Settings!
chest2.settings = {}
local settings = chest2.settings
settings.pages = minetest.settings:get("chest2.max_pages")
if settings.pages == nil then
settings.pages = 3
minetest.settings:set("chest2.max_pages", 3)
end
settings.craft_chest = minetest.settings:get_bool("chest2.craft_chest")
if settings.craft_chest == nil then
settings.craft_chest = false
minetest.settings:set_bool("chest2.craft_chest", false)
end
settings.craft_remote = minetest.settings:get_bool("chest2.craft_remote")
if settings.craft_remote == nil then
settings.craft_remote = false
minetest.settings:set_bool("chest2.craft_remote", false)
end

0
settingtypes.txt Normal file
View File

BIN
textures/chest2_front.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
textures/chest2_plate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
textures/chest2_side0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
textures/chest2_side1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

80
tool_belt.lua Normal file
View File

@ -0,0 +1,80 @@
-- A collection of utility functions for making lua less difficult
chest2.tools = {}
local tools = chest2.tools
-- Centralize logging
tools.log = function (input)
minetest.log("action", "[chest2] "..tostring(input))
end
-- Centralize errors
tools.error = function (input)
error("[chest2] "..tostring(input))
end
-- Returns a table of the string split by the given seperation string
tools.split = function (inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
-- Converts the given string so the first letter is uppercase (Returns the converted string)
tools.firstToUpper = function (str)
return (str:gsub("^%l", string.upper))
end
-- Returns space seperated position (Also strips extra precission)
tools.pos2str = function (pos)
-- Because minetest's pos_to_string is a bit harder to parse
return "" .. tostring(math.floor(pos.x)) .. " " .. tostring(math.floor(pos.y)) .. " " .. tostring(math.floor(pos.z))
end
-- Returns a xyz vector from space seperated position
tools.str2pos = function (str)
local pos = tools.split(str, " ")
return vector.new(tonumber(pos[1]), tonumber(pos[2]), tonumber(pos[3]))
end
-- https://stackoverflow.com/questions/2282444/how-to-check-if-a-table-contains-an-element-in-lua
-- Checks if a value is in the given table (True if the value exists, False otherwise)
tools.tableContainsValue = function (table, element)
for key, value in pairs(table) do
if value == element then
return true
end
end
return false
end
tools.tableContainsKey = function (table, element)
for key, value in pairs(table) do
if key == element then
return true
end
end
return false
end
-- Given a table returns it's keys (Returns a table)
tools.tableKeys = function (t)
local keys = {}
for k, v in pairs(t) do
table.insert(v)
end
return keys
end
-- Returns whole percentage given current and max values
tools.getPercent = function (current, max)
if max == nil then
max = 100
end
return math.floor( (current / max) * 100 )
end