Jordan Irwin 2021-08-18 03:31:09 -07:00
parent a92ce7233d
commit f8064312de
13 changed files with 472 additions and 178 deletions

View File

@ -32,7 +32,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
* commerce/
* [atm][] ([GPL][lic.gpl3.0]) -- version: [1.0.4][ver.atm] *2021-04-21* ([patched][patch.atm])
* [currency][] ([LGPL][lic.lgpl3.0] / [CC BY-SA][lic.ccbysa4.0]) -- version: [2021-01-30][ver.currency]
* [server_shop][] ([MIT][lic.server_shop]) -- version: [1.5][ver.server_shop] *2021-08-15*
* [server_shop][] ([MIT][lic.server_shop]) -- version: [1.6][ver.server_shop] *2021-08-18*
* core/
* [antum][] ([MIT][lic.antum]) -- version: [69b39a4 Git][ver.antum] *2017-08-30*
* [minetest_game][] ([LGPL][lic.lgpl2.1] / [CC BY-SA][lic.ccbysa3.0]) -- version: [9270188 Git][ver.minetest_game] *2021-07-08*
@ -672,7 +672,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
[ver.quartz]: https://github.com/minetest-mods/quartz/tree/72ec06f
[ver.rainbow_ore]: https://github.com/FsxShader2012/rainbow_ore/tree/830ac09
[ver.sand_monster]: https://github.com/AntumMT/mod-mob_sand_monster/tree/3dd5954
[ver.server_shop]: https://github.com/AntumMT/mod-server_shop/releases/tag/v1.5
[ver.server_shop]: https://github.com/AntumMT/mod-server_shop/releases/tag/v1.6
[ver.sfinv]: https://github.com/rubenwardy/sfinv/tree/556f70e
[ver.sfinv_buttons]: http://repo.or.cz/minetest_sfinv_buttons.git/tree/ebb1f7c
[ver.signs_lib]: https://gitlab.com/VanessaE/signs_lib/tree/2021-03-04-2

View File

@ -10,7 +10,7 @@ No craft recipe is given as this for administrators, currently a shop can only b
### Usage:
#### Registering Shops:
#### Registering Shops via API:
There are two types of shops, seller & buyer. A seller shop can be registered with `server_shop.register_seller(id, name, products)`. A buyer with `server_shop.register_buyer(id, name, products)`. `id` is a string identifier associated with the shop list. `name` is a human-readable string that will be displayed as the shop's title. `products` is the shop list definition. Shop lists are defined in a table of tuples in `{itemname, value}` format. `itemname` is the technical string name of an item (e.g. `default:wood`). `value` is the number representation of what the item is worth.
@ -26,6 +26,8 @@ server_shop.register_buyer("julie", "Julie's Shop", {
})
```
#### Registering Shops via Configuration:
Shops can optionally be configured in `<world_path>/server_shops.json` file. To register a shop, set `type` to "sell" or "buy". `id` is a string identifier for the shop. `name` is the string displayed in the formspec & when a player points at the node. `products` is an array of products sold at the shop in format "name,value".
*Example:*
@ -50,6 +52,33 @@ Shops can optionally be configured in `<world_path>/server_shops.json` file. To
]
```
#### Registering Shops via Chat Command:
The `server_shop` chat command is available to administrators with the `server` privilege. This is used for administering shops & updating configuration.
Usage:
```
/server_shop <command> [<params>]
# Commands:
/server_shop reload
- reloads data from configuration file
/server_shop register <id> <type> <name> [product1=value,product2=value,...]
- registers a shop & updates configuration file
- parameters:
- id: shop identifier
- type: can be "buy" or "sell"
- name: displayed shop name ("_" is replaced with " ")
- product list: comma-separated list in format "item=value"
/server_shop unregister <id>
- unregisters a shop & updates configuration file
- parameters:
- id: shop identifier
```
#### Registering Currencies:
Currencies can be registered with `server_shop.register_currency`:
@ -106,9 +135,11 @@ For buyer shops, the product list shows what items can be sold to this shop & ho
### Dependencies:
- Required:
- none
- simple_models
- wdata
- Optional:
- [currency][mod.currency]
- sounds
### Links:

View File

@ -6,19 +6,15 @@ TODO:
- optimize how refunds are given (e.g. if there is no room for 50s, but room for 1s, then give out 1s instead)
- Aestethics:
- make colorable with unifieddyes
- add sounds
- fix stepping sound for "large" node
- Misc.:
- make usable with "folks" mod
- set shop name from formspec instead of registration so shops with different names can use same content
- add player shops
- fix so unknown items don't mess up shop (may become a problem when shops are registered live)
- add chat command to reload shops file in world directory
- allow shops to be configured live
- register chat command "/server_shop" to register live
- "/server_shop register <buyer>|<seller> <id>"
- "/server_shop unregister <buyer>|<seller> <id>"
- "/server_shop add <buyer>|<seller> <id> <item> <price>"
- "/server_shop remove <buyer>|<seller> <id> <item>
- "/server_shop reload"
- use a spinner to select custom amount (example in terraform: https://github.com/x2048/terraform/blob/4b36d36/init.lua#L316 )
- support groups in buyer shops

View File

@ -111,6 +111,10 @@ ss.register = function(id, name, products, buyer)
products = name.products
buyer = name.buyer
name = name.name
if name.type then
buyer = name.type == "buy"
end
end
if type(id) ~= "string" then
@ -253,3 +257,213 @@ ss.is_shop_owner = function(pos, player)
local meta = core.get_meta(pos)
return player:get_player_name() == meta:get_string("owner")
end
local shops_file = core.get_worldpath() .. "/server_shops.json"
local function shop_file_error(msg)
ss.log("error", shops_file .. ": " .. msg)
end
--- Loads configuration from world path.
--
-- Configuration file is <world\_path>/server\_shops.json
--
-- @function server_shop.file_load
ss.file_load = function()
local shops_data = wdata.read("server_shops") or {}
for _, shop in ipairs(shops_data) do
if shop.type == "currency" then
ss.log("warning", "using \"currency\" key in server_shops.json is deprecated, please use \"currencies\"")
if type(shop.value) ~= "number" or shop.value <= 0 then
shop_file_error("invalid or undeclared currency \"value\"; must be a number greater than 0")
end
ss.register_currency(shop.name, shop.value)
elseif shop.type == "currencies" then
if not shop.currencies then shop.currencies = shop.value end -- allow "value" to be used instead of "currencies"
for k, v in pairs(shop.currencies) do
ss.register_currency(k, v)
end
elseif shop.type == "suffix" then
if type(shop.value) ~= "string" or shop.value:trim() == "" then
shop_file_error("invalid or undeclared suffix \"value\"; must be non-empty string")
else
ss.currency_suffix = shop.value
end
elseif shop.type == "sell" or shop.type == "buy" then
if type(shop.id) ~= "string" or shop.id:trim() == "" then
shop_file_error("invalid or undeclared \"id\"; must be non-empty string")
elseif type(shop.name) ~= "string" or shop.name:trim() == "" then
shop_file_error("invalid or undeclared \"name\"; must be non-empty string")
elseif type(shop.products) ~= "table" then
shop_file_error("invalid or undeclared \"products\" list; must be non-empty table")
else
if not shop.products then shop.products = {} end
if #shop.products == 0 then
ss.log("warning", shops_file .. ": empty shop list for shop id \"" .. shop.id .. "\"")
end
if shop.type == "sell" then
server_shop.register_seller(shop.id, shop.name, shop.products)
else
server_shop.register_buyer(shop.id, shop.name, shop.products)
end
end
elseif not shop.type then
ss.log("error", shops_file .. ": mandatory \"type\" parameter not set")
else
ss.log("error", shops_file .. ": Unrecognized type: " .. shop.type)
end
end
end
--- Permanently registers a shop.
--
-- @function server_shop.file_register
-- @tparam string id Shop identifier.
-- @tparam ShopDef def Shop definition.
ss.file_register = function(id, def)
local shops_data = wdata.read("server_shops") or {}
local existing = {}
for idx=1, #shops_data do
local entry = shops_data[idx]
if (entry.type == "sell" or entry.type == "buy") and id == entry.id then
table.insert(existing, 1, idx)
end
end
for _, idx in ipairs(existing) do
table.remove(shops_data, idx)
end
def.id = id
table.insert(shops_data, def)
wdata.write("server_shops", shops_data)
end
--- Permanently adds a product to a shop.
--
-- @function server_shop.file_add_product
-- @tparam string id Shop identifier.
-- @tparam string name Item technical name.
-- @tparam int value Item value.
-- @tparam[opt] int idx Shop index for item placement.
ss.file_add_product = function(id, name, value, idx)
local shops_data = wdata.read("server_shops") or {}
local target_shop
for idx=1, #shops_data do
local entry = shops_data[idx]
if entry.type == "sell" or entry.type == "buy" then
if id == entry.id then
target_shop = entry
break
end
end
end
if not target_shop then
ss.log("error", "cannot add item to non-registered shop ID: " .. id)
return false
end
-- FIXME: check for duplicates
if not idx or idx > #target_shop.products then
table.insert(target_shop.products, {name, value})
else
table.insert(target_shop.products, idx, {name, value})
end
ss.register(id, target_shop)
wdata.write("server_shops", shops_data)
end
--- Unregisters a shop & updates configuration.
--
-- @function server_shop.file_unregister
-- @tparam string id Shop identifier.
-- @treturn bool
ss.file_unregister = function(id)
local shops_data = wdata.read("server_shops") or {}
local unregister_idx
for idx=1, #shops_data do
local entry = shops_data[idx]
if entry.type == "sell" or entry.type == "buy" then
if id == entry.id then
unregister_idx = idx
break
end
end
end
if not unregister_idx then
ss.log("warning", "cannot unregister unknown shop ID: " .. id)
return false
end
table.remove(shops_data, unregister_idx)
ss.unregister(id)
wdata.write("server_shops", shops_data)
return true
end
--- Prunes unknown items & updates aliases in shops.
--
-- @function server_shop.prune_shops
ss.prune_shops = function()
-- show warning if no currencies are registered
if not ss.currency_is_registered() then
ss.log("warning", "no currencies registered")
else
local have_ones = false
for k, v in pairs(ss.get_currencies()) do
have_ones = v == 1
if have_ones then break end
end
if not have_ones then
ss.log("warning", "no currency registered with value 1, players may not be refunded all of their money")
end
end
-- prune unregistered items
for id, def in pairs(ss.get_shops()) do
local pruned = false
for idx = #def.products, 1, -1 do
local pname = def.products[idx][1]
local value = def.products[idx][2]
if not core.registered_items[pname] then
ss.log("warning", "removing unregistered item \"" .. pname
.. "\" from seller shop id \"" .. id .. "\"")
table.remove(def.products, idx)
pruned = true
elseif not value then
ss.log("warning", "removing item \"" .. pname
.. "\" without value from seller shop id \"" .. id .. "\"")
table.remove(def.products, idx)
pruned = true
end
-- check aliases
local alias_of = core.registered_aliases[pname]
if alias_of then
ss.log("action", "replacing alias \"" .. pname .. "\" with \"" .. alias_of
.. "\" in seller shop id \"" .. id .. "\"")
table.remove(def.products, idx)
table.insert(def.products, idx, {alias_of, value})
pruned = true
end
end
if pruned then
ss.unregister(id)
ss.register(id, def)
end
end
end

View File

@ -1,4 +1,15 @@
v1.6
----
- model moved to simple_models mod
- added node sounds
- uses wdata for reading/writing shops config
- added "server_shop" chat command:
- reload: reloads shops configuration
- register: registers new shop & updates configuration
- unregister: unregister shop id & updates configuration
v1.5
----
- added "textdomain" line to localization template

View File

@ -0,0 +1,138 @@
--- Server Shops Chat Commands
--
-- @topic commands
local ss = server_shop
local S = core.get_translator(ss.modname)
local command_list = {"reload", "register", "unregister"}
local commands = {
reload = "",
register = "<"..S("ID")..">".." <sell/buy> ".."<"..S("name")..">"
.." ["..S("product1=value,product2=value,...").."]",
unregister = "<"..S("ID")..">",
}
local format_usage = function(cmd)
local usage = S("Usage:").."\n /"..ss.modname.." "..cmd
local params = commands[cmd]
if params and params ~= "" then
usage = usage.." "..params
end
return usage
end
--- Manages shops & config.
--
-- @chatcmd server_shop
-- @param command Command to execute.
-- @param[opt] params Parameters associated with command.
-- @usage
-- /server_shop <command> [<params>]
--
-- Commands:
-- - reload
-- - Reloads shops configuration.
-- - register
-- - Registers new shop & updates configuration.
-- - parameters: <id> <sell/buy> <name> [product1=value,product2=value,...]
-- - unregister
-- - Unregisters shop & updates configuration.
-- - parameters: <id>
core.register_chatcommand(ss.modname, {
description = S("Manage shops configuration."),
privs = {server=true},
params = "<"..S("command").."> ["..S("params").."]",
func = function(name, param)
local params = param:split(" ")
local cmd = params[1]
table.remove(params, 1)
if not cmd then
return false, S("Must provide a command: @1", table.concat(command_list, ", "))
end
if cmd == "reload" then
if #params > 0 then
return false, S('"@1" command takes no parameters.', cmd).."\n\n"..format_usage(cmd)
end
ss.file_load()
ss.prune_shops()
return true, S("Shops configuration loaded.")
elseif cmd == "register" then
if #params > 4 then
return false, S("Too many parameters.").."\n\n"..format_usage(cmd)
end
local shop_id = params[1]
local shop_type = params[2]
local shop_name = params[3]
local shop_products = params[4]
if not shop_id then
return false, S("Must provide ID.").."\n\n"..format_usage(cmd)
elseif not shop_type then
return false, S("Must provide type.").."\n\n"..format_usage(cmd)
elseif not shop_name then
return false, S("Must provide name.").."\n\n"..format_usage(cmd)
end
if shop_type ~= "sell" and shop_type ~= "buy" then
return false, S('Shop type must be "@1" or "@2".', "sell", "buy").."\n\n"..format_usage(cmd)
end
shop_name = shop_name:gsub("_", " ")
local products = {}
if shop_products then
shop_products = shop_products:split(",")
for _, p in ipairs(shop_products) do
local item = p:split("=")
local item_name = item[1]
local item_value = tonumber(item[2])
if not core.registered_items[item_name] then
return false, S('"@1" is not a recognized item.', item_name).."\n\n"..format_usage(cmd)
elseif not item_value then
return false, S("Item value must be a number.").."\n\n"..format_usage(cmd)
end
table.insert(products, {item_name, item_value})
end
end
ss.file_register(shop_id, {
type = shop_type,
name = shop_name,
products = products,
})
ss.file_load()
ss.prune_shops()
return true, S("Registered shop with ID: @1", shop_id)
elseif cmd == "unregister" then
if #params > 1 then
return false, S("Too many parameters.").."\n\n"..format_usage(cmd)
end
local shop_id = params[1]
if not shop_id then
return false, S("Must provide ID.").."\n\n"..format_usage(cmd)
end
if not ss.file_unregister(shop_id) then
return false, S("Cannot unregister shop with ID: @1", shop_id)
end
return true, S("Unregistered shop with ID: @1", shop_id)
end
return false, S("Unknown command: @1", cmd)
end,
})

View File

@ -154,7 +154,7 @@ end
--
-- @function server_shop.get_formspec
-- @tparam vector pos Shop node coordinates.
-- @tparam player ObjectRef Player to whom the formspec is shown.
-- @tparam ObjectRef player Player to whom the formspec is shown.
-- @tparam[opt] bool buyer `true` if the shop in question is a buyer shop (default: false).
-- @treturn string Formspec formatted string.
ss.get_formspec = function(id, player, buyer)
@ -274,7 +274,7 @@ end
--
-- @function server_shop.show_formspec
-- @tparam vector pos Shop node coordinates.
-- @tparam player ObjectRef Player to whom the formspec is shown.
-- @tparam ObjectRef player Player to whom the formspec is shown.
-- @tparam[opt] bool buyer (deprecated)
ss.show_formspec = function(pos, player, buyer)
if buyer ~= nil then

View File

@ -25,132 +25,13 @@ local scripts = {
"deposit",
"formspec",
"node",
"command",
}
for _, script in ipairs(scripts) do
dofile(ss.modpath .. "/" .. script .. ".lua")
end
-- load configured shops from world directory
local shops_file = core.get_worldpath() .. "/server_shops.json"
local function shop_file_error(msg)
error(shops_file .. ": " .. msg)
end
local fopen = io.open(shops_file, "r")
if fopen ~= nil then
local content = fopen:read("*a")
io.close(fopen)
local json = core.parse_json(content)
if json then
for _, shop in ipairs(json) do
if shop.type == "currency" then
ss.log("warning", "using \"currency\" key in server_shops.json is deprecated, please use \"currencies\"")
if type(shop.value) ~= "number" or shop.value <= 0 then
shop_file_error("invalid or undeclared currency \"value\"; must be a number greater than 0")
end
ss.register_currency(shop.name, shop.value)
elseif shop.type == "currencies" then
if not shop.currencies then shop.currencies = shop.value end -- allow "value" to be used instead of "currencies"
for k, v in pairs(shop.currencies) do
ss.register_currency(k, v)
end
elseif shop.type == "suffix" then
if type(shop.value) ~= "string" or shop.value:trim() == "" then
shop_file_error("invalid or undeclared suffix \"value\"; must be non-empty string")
else
ss.currency_suffix = shop.value
end
elseif shop.type == "sell" or shop.type == "buy" then
if type(shop.id) ~= "string" or shop.id:trim() == "" then
shop_file_error("invalid or undeclared \"id\"; must be non-empty string")
elseif type(shop.name) ~= "string" or shop.name:trim() == "" then
shop_file_error("invalid or undeclared \"name\"; must be non-empty string")
elseif type(shop.products) ~= "table" then
shop_file_error("invalid or undeclared \"products\" list; must be non-empty table")
else
if not shop.products then shop.products = {} end
if #shop.products == 0 then
ss.log("warning", shops_file .. ": empty shop list for shop id \"" .. shop.id .. "\"")
end
if shop.type == "sell" then
server_shop.register_seller(shop.id, shop.name, shop.products)
else
server_shop.register_buyer(shop.id, shop.name, shop.products)
end
end
elseif not shop.type then
error(shops_file .. ": mandatory \"type\" parameter not set")
else
error(shops_file .. ": Unrecognized type: " .. shop.type)
end
end
end
else
-- create file if doesn't exist
fopen = io.open(shops_file, "w")
if fopen == nil then
server_shop.log("error", "Could not create " .. shops_file .. ", directory exists")
else
io.close(fopen)
end
end
core.register_on_mods_loaded(function()
-- show warning if no currencies are registered
if not ss.currency_is_registered() then
ss.log("warning", "no currencies registered")
else
local have_ones = false
for k, v in pairs(ss.get_currencies()) do
have_ones = v == 1
if have_ones then break end
end
if not have_ones then
ss.log("warning", "no currency registered with value 1, players may not be refunded all of their money")
end
end
-- prune unregistered items
for id, def in pairs(ss.get_shops()) do
local pruned = false
for idx = #def.products, 1, -1 do
local pname = def.products[idx][1]
local value = def.products[idx][2]
if not core.registered_items[pname] then
ss.log("warning", "removing unregistered item \"" .. pname
.. "\" from seller shop id \"" .. id .. "\"")
table.remove(def.products, idx)
pruned = true
elseif not value then
-- FIXME: this should be done in registration method
ss.log("warning", "removing item \"" .. pname
.. "\" without value from seller shop id \"" .. id .. "\"")
table.remove(def.products, idx)
pruned = true
end
-- check aliases
local alias_of = core.registered_aliases[pname]
if alias_of then
ss.log("action", "replacing alias \"" .. pname .. "\" with \"" .. alias_of
.. "\" in seller shop id \"" .. id .. "\"")
table.remove(def.products, idx)
table.insert(def.products, idx, {alias_of, value})
pruned = true
end
end
if pruned then
ss.unregister(id)
ss.register(id, def)
end
end
end)
ss.file_load()
core.register_on_mods_loaded(ss.prune_shops)

View File

@ -15,3 +15,32 @@ You haven't deposited enough money.=No has depositado suficiente dinero.
You purchased @1 @2 for @3 @4.=Compraste @1 @2 por @3 @4.
You sold @1 @2 for @3 @4.=Vendiste @1 @2 por @3 @4.
WARNING: @1 @2 was dropped on the ground.=AVISO: @1 @2 se cayó en la tierra.
# chat commands
Manage shops configuration.=Administrar configuración de tiendas.
Usage:=Uso:
command=orden
params=parámetros
Must provide a command: @1=Debe producir un orden: @1
"@1" command takes no parameters.=Orden "@1" no requiere parametros.
Too many parameters.=Demasiado parámetros.
Unknown command: @1=Orden desconocido: @1
Must provide ID.=Se requiere ID.
# reload command
Shops configuration loaded.=Configuración de tiendas cargada.
# register command
ID=
name=nombre
product1@=value,product2@=value,...=producto1@=valor,producto2@=valor,...
Must provide type.=Se requiere tipo.
Must provide name.=Se requiere nombre.
Shop type must be "@1" or "@2".=Tipo de tienda debe ser "@1" o "@2".
"@1" is not a recognized item.="@1" no es objeto conocido.
Item value must be a number.=Valor de objeto debe ser número.
Registered shop with ID: @1=Agregó al registro tienda con ID: @1
# unregister command
Cannot unregister shop with ID: @1=No puede quitar del registro tienda con ID: @1
Unregistered shop with ID: @1=Se quitó del registro tienda con ID: @1

View File

@ -15,3 +15,32 @@ You haven't deposited enough money.=
You purchased @1 @2 for @3 @4.=
You sold @1 @2 for @3 @4.=
WARNING: @1 @2 was dropped on the ground.=
# chat commands
Manage shops configuration.=
Usage:=
command=
params=
Must provide a command: @1=
"@1" command takes no parameters.=
Too many parameters.=
Unknown command: @1=
Must provide ID.=
# reload command
Shops configuration loaded.=
# register command
ID=
name=
product1@=value,product2@=value,...=
Must provide type.=
Must provide name.=
Shop type must be "@1" or "@2".=
"@1" is not a recognized item.=
Item value must be a number.=
Registered shop with ID: @1=
# unregister command
Cannot unregister shop with ID: @1=
Unregistered shop with ID: @1=

View File

@ -1,7 +1,8 @@
name = server_shop
title = Server Shop
description = Shops intended to be set up by server administrators.
version = 1.5
version = 1.6
author = Jordan Irwin (AntumDeluge)
min_minetest_version = 5.0
optional_depends = currency
depends = simple_models, wdata
optional_depends = currency, sounds

View File

@ -1,42 +0,0 @@
# Blender v2.93.2 OBJ File: 'server_shop_2.blend'
# www.blender.org
mtllib server_shop.mtl
o Cube
v 0.500000 -0.500000 -0.500000
v 0.500000 1.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v -0.500000 1.500000 -0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 1.500000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 1.500000 0.500000
vt 0.500000 0.250043
vt 0.500000 0.000087
vt 0.749957 0.000087
vt 0.749957 0.250043
vt 0.749957 0.749978
vt 0.749957 0.250065
vt 0.999914 0.250065
vt 0.999914 0.749978
vt 0.000087 0.749978
vt 0.000087 0.250065
vt 0.250043 0.250065
vt 0.250043 0.749978
vt 0.500000 0.999957
vt 0.500000 0.750000
vt 0.749957 0.999957
vt 0.500000 0.250065
vn -0.0000 -1.0000 -0.0000
vn -1.0000 0.0000 0.0000
vn 0.0000 -0.0000 1.0000
vn 0.0000 1.0000 0.0000
vn -0.0000 0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
usemtl Material
s off
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/6/2 7/7/2 8/8/2
f 8/9/3 7/10/3 5/11/3 6/12/3
f 6/13/4 2/14/4 4/5/4 8/15/4
f 2/14/5 1/16/5 3/6/5 4/5/5
f 6/12/6 5/11/6 1/16/6 2/14/6

View File

@ -8,11 +8,17 @@ local ss = server_shop
local S = core.get_translator(ss.modname)
local node_sound
if core.global_exists("sounds") then
node_sound = sounds.node_stone()
end
local def = {
base = {
description = S("Shop"),
groups = {oddly_breakable_by_hand=1},
paramtype2 = "facedir",
sounds = node_sound,
after_place_node = function(pos, placer)
-- set node owner
core.get_meta(pos):set_string("owner", placer:get_player_name())
@ -54,7 +60,7 @@ local def = {
-- @img server_shop_front.png
large = {
drawtype = "mesh",
mesh = "server_shop.obj",
mesh = "node_1x2x1.obj",
tiles = {"server_shop_mesh.png",},
selection_box = {
type = "fixed",