Update server_shop to v1.1...
Previous update was versioned wrong. Release: https://github.com/AntumMT/mod-server_shop/releases/tag/v1.1master
parent
d7f811e138
commit
6e3105b67a
|
@ -19,7 +19,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
* [override][] ([MIT][lic.override]) -- version: [0.2 (e6dda7a Git)][ver.override] *2017-08-30*
|
||||
* [privilegeareas][] ([WTFPL][lic.privilegeareas] / [CC0][lic.cc0]) -- version: [15eae20 Git][ver.privilegeareas] *2018-11-16*
|
||||
* [privs][] ([CC0][lic.cc0])
|
||||
* [server_shop][] ([MIT][lic.server_shop]) -- version: [1.1][ver.server_shop] *2021-04-29*
|
||||
* [server_shop][] ([MIT][lic.server_shop]) -- version: [1.1][ver.server_shop] *2021-05-11*
|
||||
* [spectator_mode][] ([WTFPL][lic.spectator_mode]) -- version: [3648371 Git][ver.spectator_mode] *2020-07-15*
|
||||
* [whitelist][] ([CC0][lic.cc0]) -- version: [0.1 (b813b19 Git)][ver.whitelist] *2017-08-18*
|
||||
* [antum][] ([MIT][lic.antum]) -- version: [69b39a4 Git][ver.antum] *2017-08-30*
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
|
||||
1.1
|
||||
- use json format for shops configuration in world directory
|
||||
- switched id & name parameters positions in register_shop
|
||||
- show preview image of selected item
|
||||
- node owners can't set ID unless they have "server" priv
|
||||
|
||||
|
||||
1.0
|
||||
- created node
|
||||
- created simple textures
|
||||
- formspec displays items & prices of associated shop
|
||||
- minegeld notes can be deposited & refunded
|
||||
- shops are configured from world directory
|
||||
- players with "server" priv alter
|
||||
- players with "server" priv or node owners can set ID
|
||||
- players with "server" priv or node owners can dig
|
||||
- implemented deposit, purchase, & refund functionality
|
||||
|
|
|
@ -12,11 +12,11 @@ No craft recipe is given as this for administrators, currently a machine can onl
|
|||
|
||||
#### Usage:
|
||||
|
||||
Shop lists are registered with the `server_shop.register_shop(name, id, def)` function. `name` is a human-readable string that will be displayed as the shop's title. `id` is a string identifier associated with the shop list. `def` is the shop list definition. Shop lists are defined in a table of tupes in `{itemname, price}` format.
|
||||
Shop lists are registered with the `server_shop.register_shop(id, name, def)` function. `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. `def` is the shop list definition. Shop lists are defined in a table of tuples in `{itemname, price}` format.
|
||||
|
||||
Registration example:
|
||||
```
|
||||
server_shop.register_shop("Basic", "basic", {
|
||||
server_shop.register_shop("basic", "Basic Shop", {
|
||||
{
|
||||
{"default:wood", 2},
|
||||
{"default:obsidian", 7},
|
||||
|
@ -24,7 +24,27 @@ server_shop.register_shop("Basic", "basic", {
|
|||
})
|
||||
```
|
||||
|
||||
Shops can optionally be registered in `<world_path>/server_shops.lua` file (this will be changed in the future to use configuration instead of Lua code).
|
||||
Shops can optionally be registered in `<world_path>/server_shops.json` file. Example:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id":"frank",
|
||||
"name":"Frank's Shop",
|
||||
"sells":
|
||||
{"default:wood":1}
|
||||
},
|
||||
{
|
||||
"id":"julie",
|
||||
"name":"Julie's Shop",
|
||||
"sells":
|
||||
{
|
||||
"default:iron_lump":5,
|
||||
"default:copper_lump":5,
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Server admins use the chat command `/giveme server_shop:shop` to receive a shop node. After placing the node, the ID can be set with the "Set ID" button & text input field (only players with the "server" privilege can set ID). Set the ID to the shop ID you want associated with this shop node ("basic" for the example above) & the list will be populated with the registered products & prices.
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
TODO:
|
||||
- Security:
|
||||
- don't allow other players to interfere with transactions
|
||||
- make loading world directory file more secure (don't use raw Lua)
|
||||
- "Set ID" button is displayed to other players if admin makes changes while formspec visible
|
||||
- Functionality:
|
||||
- add option to buy multiple of an item
|
||||
- optimize how refunds are given (e.g. if there is no room for 50s, but room for 1s, then give out 1s instead)
|
||||
- add shop for players to receive money for selling items
|
||||
- Visuals:
|
||||
- touch up textures
|
||||
- fix texture tiling
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
--- API
|
||||
--
|
||||
|
||||
local ss = server_shop
|
||||
|
||||
|
||||
local shops = {}
|
||||
|
||||
--- Registers a shop list to be accessed via a shop node.
|
||||
--
|
||||
-- @function server_shop.register_shop
|
||||
-- @param id String ID associated with shop list.
|
||||
-- @param name Human readable name to be displayed.
|
||||
-- @param def Shop definition (e.g. items & prices)
|
||||
function ss.register_shop(id, name, def)
|
||||
if shops[id] then
|
||||
ss.log("warning", "Overwriting shop with id: " .. id)
|
||||
end
|
||||
|
||||
local new_shop = {name=name, def=def,}
|
||||
shops[id] = new_shop
|
||||
|
||||
ss.log("action", "Registered shop: " .. id)
|
||||
end
|
||||
|
||||
--- Retrieves shop by ID.
|
||||
--
|
||||
-- @function server_shop.get_shop
|
||||
-- @param id String identifier of shop.
|
||||
-- @return Table of shop contents.
|
||||
function ss.get_shop(id)
|
||||
return shops[id]
|
||||
end
|
||||
|
||||
--- Checks if a player has admin rights to for managing shop.
|
||||
--
|
||||
-- @function server_shoip.is_shop_admin
|
||||
-- @param pos Position of shop.
|
||||
-- @param player Player requesting permissions.
|
||||
-- @return `true` if player has *server* priv.
|
||||
function ss.is_shop_admin(pos, player)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
return core.check_player_privs(player, "server")
|
||||
end
|
||||
|
||||
--- Checks if a player is the owner of node.
|
||||
--
|
||||
-- @function server_shop.is_shop_owner
|
||||
-- @param pos Position of shop node.
|
||||
-- @param player Player to be checked.
|
||||
-- @return `true` if player is owner.
|
||||
function ss.is_shop_owner(pos, player)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
return player:get_player_name() == meta:get_string("owner")
|
||||
end
|
|
@ -0,0 +1,122 @@
|
|||
|
||||
local ss = server_shop
|
||||
|
||||
|
||||
local fs_width = 14
|
||||
local fs_height = 11
|
||||
local btn_w = 1.75
|
||||
local btn_y = 4.6
|
||||
|
||||
--- Retrieves shop product list by ID.
|
||||
--
|
||||
-- @function get_product_list
|
||||
-- @local
|
||||
-- @param id String identifier of shop.
|
||||
-- @return String of shop contents.
|
||||
function get_product_list(id)
|
||||
local products = ""
|
||||
local shop = ss.get_shop(id)
|
||||
|
||||
if shop and shop.def then
|
||||
for _, p in ipairs(shop.def) do
|
||||
local item = core.registered_items[p[1]]
|
||||
|
||||
if not item then
|
||||
core.log("warning", "Unknown item \"" .. p[1] .. "\" for shop ID \"" .. id .. "\"")
|
||||
goto continue
|
||||
end
|
||||
|
||||
local item_name = item.short_description
|
||||
if not item_name then
|
||||
item_name = item.description
|
||||
if not item_name then
|
||||
item_name = p[1]
|
||||
end
|
||||
end
|
||||
|
||||
local item_price = p[2]
|
||||
if not item_price then
|
||||
core.log("warning", "Price not set for item \"" .. p[1] .. "\" for shop ID \"" .. id .. "\"")
|
||||
goto continue
|
||||
end
|
||||
|
||||
if products == "" then
|
||||
products = item_name .. " : " .. tostring(item_price) .. " MG"
|
||||
else
|
||||
products = products .. "," .. item_name .. " : " .. tostring(item_price) .. " MG"
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
return products
|
||||
end
|
||||
|
||||
|
||||
function server_shop.get_formspec(pos, player)
|
||||
local meta = core.get_meta(pos)
|
||||
local id = meta:get_string("id")
|
||||
local deposited = meta:get_int("deposited")
|
||||
|
||||
local formspec = "formspec_version[4]size[" .. tostring(fs_width) .. "," .. tostring(fs_height) .."]"
|
||||
|
||||
local shop_name = meta:get_string("name"):trim()
|
||||
if shop_name ~= "" then
|
||||
formspec = formspec .. "label[0.2,0.4;" .. shop_name .. "]"
|
||||
end
|
||||
|
||||
if ss.is_shop_admin(pos, player) then
|
||||
formspec = formspec
|
||||
.. "button[" .. tostring(fs_width-6.2) .. ",0.2;" .. tostring(btn_w) .. ",0.75;btn_id;Set ID]"
|
||||
.. "field[" .. tostring(fs_width-4.3) .. ",0.2;4.1,0.75;input_id;;" .. id .. "]"
|
||||
.. "field_close_on_enter[input_id;false]"
|
||||
end
|
||||
|
||||
-- ensure selected value in meta data
|
||||
if meta:get_int("selected") < 1 then
|
||||
meta:set_int("selected", meta:get_int("default_selected"))
|
||||
end
|
||||
|
||||
-- get item name for displaying image
|
||||
local selected_item = nil
|
||||
local shop = ss.get_shop(id)
|
||||
|
||||
if shop then
|
||||
-- make sure we're not out of range of the shop list
|
||||
local s_idx = meta:get_int("selected")
|
||||
if s_idx > #shop.def then
|
||||
s_idx = 1
|
||||
end
|
||||
|
||||
selected_item = shop.def[meta:get_int("selected")]
|
||||
if selected_item then
|
||||
selected_item = selected_item[1]
|
||||
end
|
||||
end
|
||||
|
||||
local margin_r = fs_width-(btn_w+0.2)
|
||||
|
||||
formspec = formspec
|
||||
.. "label[0.2,1;Deposited: " .. tostring(deposited) .. " MG]"
|
||||
.. "list[context;deposit;0.2,1.5;1,1;0]"
|
||||
.. "textlist[2.15,1.5;9.75,3;products;" .. get_product_list(id) .. ";"
|
||||
.. tostring(meta:get_int("selected")) .. ";false]"
|
||||
|
||||
if selected_item and selected_item ~= "" then
|
||||
formspec = formspec
|
||||
.. "item_image[" .. tostring(fs_width-1.2) .. ",1.5;1,1;" .. selected_item .. "]"
|
||||
end
|
||||
|
||||
formspec = formspec
|
||||
.. "button[0.2," .. tostring(btn_y) .. ";" .. tostring(btn_w) .. ",0.75;btn_refund;Refund]"
|
||||
.. "button[" .. tostring(margin_r) .. "," .. tostring(btn_y) .. ";" .. tostring(btn_w) .. ",0.75;btn_buy;Buy]"
|
||||
.. "list[current_player;main;2.15,5.5;8,4;0]"
|
||||
.. "button_exit[" .. tostring(margin_r) .. ",10;" .. tostring(btn_w) .. ",0.75;btn_close;Close]"
|
||||
local formname = "server_shop"
|
||||
if id and id ~= "" then
|
||||
formname = formname .. "_" .. id
|
||||
end
|
||||
|
||||
return formspec .. formname
|
||||
end
|
|
@ -1,516 +1,55 @@
|
|||
|
||||
server_shop = {}
|
||||
server_shop.name = core.get_current_modname()
|
||||
server_shop.modname = core.get_current_modname()
|
||||
server_shop.modpath = core.get_modpath(server_shop.modname)
|
||||
|
||||
local node_name = "server_shop:shop"
|
||||
|
||||
local shops = {}
|
||||
|
||||
--- Registers a shop list to be accessed via a shop node.
|
||||
--
|
||||
-- @function server_shop.register_shop
|
||||
-- @param name Human readable name to be displayed.
|
||||
-- @param id String ID associated with shop list.
|
||||
-- @param def Shop definition (e.g. items & prices)
|
||||
function server_shop.register_shop(name, id, def)
|
||||
-- FIXME: check if shop is alreay registered
|
||||
local shop = {}
|
||||
shop.name = name
|
||||
shop.id = id
|
||||
shop.def = def
|
||||
table.insert(shops, shop)
|
||||
|
||||
core.log("action", "[" .. server_shop.name .. "] Registered shop: " .. shop.id)
|
||||
function server_shop.log(lvl, msg)
|
||||
if not msg then
|
||||
msg = lvl
|
||||
lvl = nil
|
||||
end
|
||||
|
||||
local function get_shop(id)
|
||||
for _, s in pairs(shops) do
|
||||
if s.id == id then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_shop_name(id)
|
||||
local shop = get_shop(id)
|
||||
if shop then
|
||||
return shop.name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local fs_width = 14
|
||||
local fs_height = 11
|
||||
local btn_w = 1.75
|
||||
local btn_y = 4.6
|
||||
|
||||
local function get_product_list(id)
|
||||
local products = ""
|
||||
local shop = get_shop(id)
|
||||
|
||||
if shop and shop.def then
|
||||
for _, p in ipairs(shop.def) do
|
||||
local item = core.registered_items[p[1]]
|
||||
|
||||
if not item then
|
||||
core.log("warning", "Unknown item \"" .. p[1] .. "\" for shop ID \"" .. id .. "\"")
|
||||
goto continue
|
||||
end
|
||||
|
||||
local item_name = item.short_description
|
||||
if not item_name then
|
||||
item_name = item.description
|
||||
if not item_name then
|
||||
item_name = p[1]
|
||||
end
|
||||
end
|
||||
|
||||
local item_price = p[2]
|
||||
if not item_price then
|
||||
core.log("warning", "Price not set for item \"" .. p[1] .. "\" for shop ID \"" .. id .. "\"")
|
||||
goto continue
|
||||
end
|
||||
|
||||
if products == "" then
|
||||
products = item_name .. " : " .. tostring(item_price) .. " MG"
|
||||
if not lvl then
|
||||
core.log("[" .. server_shop.modname .. "] " .. msg)
|
||||
else
|
||||
products = products .. "," .. item_name .. " : " .. tostring(item_price) .. " MG"
|
||||
end
|
||||
|
||||
::continue::
|
||||
core.log(lvl, "[" .. server_shop.modname .. "] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
return products
|
||||
end
|
||||
|
||||
--- Checks if a player has admin rights to for managing shop.
|
||||
--
|
||||
-- @local
|
||||
-- @function is_shop_admin
|
||||
-- @param pos Position of shop.
|
||||
-- @param player Player requesting permissions.
|
||||
local function is_shop_admin(pos, player)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
return core.check_player_privs(player, "server")
|
||||
--or player:get_player_name() == meta:get_string("owner")
|
||||
end
|
||||
|
||||
local function is_shop_owner(pos, player)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
return player:get_player_name() == meta:get_string("owner")
|
||||
end
|
||||
|
||||
local function get_formspec(pos, player)
|
||||
local meta = core.get_meta(pos)
|
||||
local id = meta:get_string("id")
|
||||
local deposited = meta:get_int("deposited")
|
||||
|
||||
local formspec = "formspec_version[4]size[" .. tostring(fs_width) .. "," .. tostring(fs_height) .."]"
|
||||
|
||||
local shop_name = meta:get_string("name"):trim()
|
||||
if shop_name ~= "" then
|
||||
formspec = formspec .. "label[0.2,0.4;Shop: " .. shop_name .. "]"
|
||||
end
|
||||
|
||||
if is_shop_admin(pos, player) then
|
||||
formspec = formspec
|
||||
.. "button[" .. tostring(fs_width-6.2) .. ",0.2;" .. tostring(btn_w) .. ",0.75;btn_id;Set ID]"
|
||||
.. "field[" .. tostring(fs_width-4.3) .. ",0.2;4.1,0.75;input_id;;" .. id .. "]"
|
||||
.. "field_close_on_enter[input_id;false]"
|
||||
end
|
||||
|
||||
-- ensure selected value in meta data
|
||||
if meta:get_int("selected") < 1 then
|
||||
meta:set_int("selected", meta:get_int("default_selected"))
|
||||
end
|
||||
|
||||
-- get item name for displaying image
|
||||
local selected_item = nil
|
||||
local shop = get_shop(id)
|
||||
if shop then
|
||||
selected_item = shop.def[meta:get_int("selected")][1]
|
||||
end
|
||||
|
||||
local margin_r = fs_width-(btn_w+0.2)
|
||||
|
||||
formspec = formspec
|
||||
.. "label[0.2,1;Deposited: " .. tostring(deposited) .. " MG]"
|
||||
.. "list[context;deposit;0.2,1.5;1,1;0]"
|
||||
.. "textlist[2.15,1.5;9.75,3;products;" .. get_product_list(id) .. ";"
|
||||
.. tostring(meta:get_int("selected")) .. ";false]"
|
||||
|
||||
if selected_item and selected_item ~= "" then
|
||||
formspec = formspec
|
||||
.. "item_image[" .. tostring(fs_width-1.2) .. ",1.5;1,1;" .. selected_item .. "]"
|
||||
end
|
||||
|
||||
formspec = formspec
|
||||
.. "button[0.2," .. tostring(btn_y) .. ";" .. tostring(btn_w) .. ",0.75;btn_refund;Refund]"
|
||||
.. "button[" .. tostring(margin_r) .. "," .. tostring(btn_y) .. ";" .. tostring(btn_w) .. ",0.75;btn_buy;Buy]"
|
||||
.. "list[current_player;main;2.15,5.5;8,4;0]"
|
||||
.. "button_exit[" .. tostring(margin_r) .. ",10;" .. tostring(btn_w) .. ",0.75;btn_close;Close]"
|
||||
local formname = "server_shop"
|
||||
if id and id ~= "" then
|
||||
formname = formname .. "_" .. id
|
||||
end
|
||||
|
||||
return formspec .. formname
|
||||
end
|
||||
|
||||
|
||||
local currencies = {
|
||||
{"currency:minegeld", 1,},
|
||||
{"currency:minegeld_5", 5,},
|
||||
{"currency:minegeld_10", 10,},
|
||||
{"currency:minegeld_50", 50,},
|
||||
{"currency:minegeld_100", 100,},
|
||||
local scripts = {
|
||||
"api",
|
||||
"formspec",
|
||||
"node",
|
||||
}
|
||||
|
||||
--- Calculates how much money is being deposited.
|
||||
local function calculate_value(stack)
|
||||
local value = 0
|
||||
for _, c in ipairs(currencies) do
|
||||
if stack:get_name() == c[1] then
|
||||
value = stack:get_count() * c[2]
|
||||
break
|
||||
for _, script in ipairs(scripts) do
|
||||
dofile(server_shop.modpath .. "/" .. script .. ".lua")
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
--- Calculates money to be returned to player.
|
||||
--
|
||||
-- FIXME: not very intuitive
|
||||
local function calculate_refund(total)
|
||||
local refund = 0
|
||||
|
||||
local hun = math.floor(total / 100)
|
||||
total = total - (hun * 100)
|
||||
|
||||
local fif = math.floor(total / 50)
|
||||
total = total - (fif * 50)
|
||||
|
||||
local ten = math.floor(total / 10)
|
||||
total = total - (ten * 10)
|
||||
|
||||
local fiv = math.floor(total / 5)
|
||||
total = total - (fiv * 5)
|
||||
|
||||
-- at this point, 'total' should always be divisible by whole number
|
||||
local one = total / 1
|
||||
total = total - one
|
||||
|
||||
if total ~= 0 then
|
||||
core.log("warning", "Refund did not result in 0 deposited balance")
|
||||
end
|
||||
|
||||
local refund = {}
|
||||
for _, c in ipairs(currencies) do
|
||||
local iname = c[1]
|
||||
local ivalue = c[2]
|
||||
local icount = 0
|
||||
|
||||
if ivalue == 1 then
|
||||
icount = one
|
||||
elseif ivalue == 5 then
|
||||
icount = fiv
|
||||
elseif ivalue == 10 then
|
||||
icount = ten
|
||||
elseif ivalue == 50 then
|
||||
icount = fif
|
||||
elseif ivalue == 100 then
|
||||
icount = hun
|
||||
end
|
||||
|
||||
if icount > 0 then
|
||||
local stack = ItemStack(iname)
|
||||
stack:set_count(icount)
|
||||
table.insert(refund, stack)
|
||||
end
|
||||
end
|
||||
|
||||
return refund
|
||||
end
|
||||
|
||||
--- Calculates the price of item being purchased.
|
||||
--
|
||||
-- @function calculate_price
|
||||
-- @local
|
||||
-- @param shop_id String identifier of shop.
|
||||
-- @param item_id String identifier of item (e.g. default:dirt).
|
||||
-- @param quantity Number of item being purchased.
|
||||
-- @return Total value of purchase.
|
||||
local function calculate_price(shop_id, item_id, quantity)
|
||||
local shop = get_shop(shop_id)
|
||||
if not shop then
|
||||
return 0
|
||||
end
|
||||
|
||||
local price_per = 0
|
||||
for _, i in ipairs(shop.def) do
|
||||
if i[1] == item_id then
|
||||
price_per = i[2]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return price_per * quantity
|
||||
end
|
||||
|
||||
--- Retrieves item name/id from shop list.
|
||||
--
|
||||
-- @function get_shop_index
|
||||
-- @local
|
||||
-- @param shop_id String identifier of shop.
|
||||
-- @param idx Index of the item to retrieve.
|
||||
-- @return String item identifier or `nil` if shop does not contain item.
|
||||
local function get_shop_index(shop_id, idx)
|
||||
local shop = get_shop(shop_id)
|
||||
if shop then
|
||||
local product = shop.def[idx]
|
||||
if product then
|
||||
return product[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Add item(s) to player inventory or drops on ground.
|
||||
--
|
||||
-- @function player The player who is receiving the item.
|
||||
-- @local
|
||||
-- @param product String identifier of the item.
|
||||
-- @param quantity Amount to give.
|
||||
local function give_product(player, product, quantity)
|
||||
local istack = product
|
||||
if type(istack) == "string" then
|
||||
-- create the ItemStack
|
||||
istack = ItemStack(product)
|
||||
-- make sure we give at leaset 1
|
||||
if not quantity then quantity = 1 end
|
||||
istack:set_count(quantity)
|
||||
elseif quantity and istack:get_count() ~= quantity then
|
||||
istack:set_count(quantity)
|
||||
end
|
||||
|
||||
-- add to player inventory or drop on ground
|
||||
local pinv = player:get_inventory()
|
||||
if not pinv:room_for_item("main", istack) then
|
||||
core.chat_send_player(player:get_player_name(), "WARNING: "
|
||||
.. tostring(istack:get_count()) .. " " .. istack:get_description()
|
||||
.. " was dropped on the ground.")
|
||||
core.item_drop(istack, player, player:get_pos())
|
||||
else
|
||||
pinv:add_item("main", istack)
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the owner of the shop & gives admin privileges.
|
||||
--
|
||||
-- @local
|
||||
-- @function set_owner
|
||||
-- @param pos Position of shop.
|
||||
-- @param pname String name of new owner.
|
||||
local function set_owner(pos, pname)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("owner", pname)
|
||||
end
|
||||
|
||||
--- Sets the amount of money deposited into machine.
|
||||
--
|
||||
-- @local
|
||||
-- @function set_deposit_balance
|
||||
-- @param pos Position of shop.
|
||||
-- @param amount Integer amount to be set.
|
||||
--[[ UNUSED:
|
||||
local function set_deposit_balance(pos, amount)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_int("deposited", amount)
|
||||
end
|
||||
]]
|
||||
|
||||
--- Retrieves amound of money currently deposited into shop.
|
||||
--
|
||||
-- @local
|
||||
-- @function get_deposit_balance
|
||||
-- @param pos Position of shop.
|
||||
local function get_deposit_balance(pos)
|
||||
return core.get_meta(pos):get_int("deposited")
|
||||
end
|
||||
|
||||
core.register_node(node_name, {
|
||||
description = "Shop",
|
||||
--drawtype = "nodebox",
|
||||
drawtype = "normal",
|
||||
tiles = {
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_front.png",
|
||||
"server_shop_side.png",
|
||||
},
|
||||
--[[
|
||||
drawtype = "mesh",
|
||||
mesh = "server_shop.obj",
|
||||
tiles = {"server_shop_mesh.png",},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.5, -0.5, -0.5, 0.5, 1.5, 0.5},
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.5, -0.5, -0.5, 0.5, 1.45, 0.5},
|
||||
},
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.5, -0.5, -0.5, 0.5, 1.5, 0.5},
|
||||
},
|
||||
]]
|
||||
groups = {oddly_breakable_by_hand=1,},
|
||||
paramtype2 = "facedir",
|
||||
on_construct = function(pos)
|
||||
local meta = core.get_meta(pos)
|
||||
-- set which item should be selected when formspec is opened
|
||||
meta:set_int("default_selected", 1)
|
||||
meta:set_string("formspec", get_formspec(pos))
|
||||
end,
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = core.get_meta(pos)
|
||||
set_owner(pos, placer:get_player_name())
|
||||
end,
|
||||
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("formspec", get_formspec(pos, player))
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size("deposit", 1)
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
local meta = core.get_meta(pos)
|
||||
local deposited = meta:get_int("deposited")
|
||||
|
||||
if deposited > 0 then
|
||||
core.log(player:get_player_name() .. " attempted to dig " .. node_name
|
||||
.. " containing " .. tostring(deposited) .. " MG at ("
|
||||
.. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
|
||||
return false
|
||||
end
|
||||
|
||||
return is_shop_owner(pos, player) or is_shop_admin(pos, player)
|
||||
end,
|
||||
on_dig = function(pos, node, digger)
|
||||
local deposited = core.get_meta(pos):get_int("deposited")
|
||||
core.node_dig(pos, node, digger)
|
||||
|
||||
if deposited < 0 then
|
||||
core.log("warning", digger:get_player_name() .. " dug " .. node_name
|
||||
.. " containing negative deposit balance")
|
||||
end
|
||||
end,
|
||||
on_receive_fields = function(pos, formname, fields, sender)
|
||||
local meta = core.get_meta(pos)
|
||||
local pname = sender:get_player_name()
|
||||
|
||||
if fields.quit then
|
||||
-- reset selected to default when closed
|
||||
meta:set_int("selected", meta:get_int("default_selected"))
|
||||
elseif fields.btn_id and is_shop_admin(pos, sender) then
|
||||
local new_id = fields.input_id:trim()
|
||||
-- FIXME: allow to be set to "" in order to remove shop
|
||||
if new_id ~= "" then
|
||||
core.log("action", pname .. " sets " .. node_name .. " ID to \"" .. new_id
|
||||
.. "\" at (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
|
||||
meta:set_string("id", new_id)
|
||||
fields.input_id = new_id
|
||||
|
||||
-- set or remove displayed text when pointed at
|
||||
local shop_name = get_shop_name(new_id)
|
||||
if shop_name then
|
||||
meta:set_string("infotext", "Shop: " .. shop_name)
|
||||
meta:set_string("name", shop_name)
|
||||
else
|
||||
meta:set_string("infotext", nil)
|
||||
meta:set_string("name", nil)
|
||||
end
|
||||
end
|
||||
elseif fields.products then
|
||||
-- set selected index in meta data to be retrieved when "buy" button is pressed
|
||||
meta:set_int("selected", fields.products:sub(5))
|
||||
elseif fields.btn_refund then
|
||||
local refund = calculate_refund(meta:get_int("deposited"))
|
||||
for _, istack in ipairs(refund) do
|
||||
give_product(sender, istack)
|
||||
end
|
||||
|
||||
-- reset deposited amount after refund
|
||||
meta:set_int("deposited", 0)
|
||||
elseif fields.btn_buy then
|
||||
-- get selected index
|
||||
local selected = meta:get_int("selected")
|
||||
local shop_id = meta:get_string("id")
|
||||
local product = get_shop_index(shop_id, selected)
|
||||
|
||||
if not product then
|
||||
core.log("warning", "Trying to buy undefined item from shop \""
|
||||
.. tostring(shop_id) .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
-- FIXME: allow purchasing multiples
|
||||
local product_count = 1
|
||||
local total = calculate_price(shop_id, product, product_count)
|
||||
|
||||
local deposited = meta:get_int("deposited")
|
||||
if total > deposited then
|
||||
core.chat_send_player(pname, "You haven't deposited enough money.")
|
||||
return
|
||||
end
|
||||
|
||||
product = ItemStack(product)
|
||||
product:set_count(product_count)
|
||||
|
||||
-- subtract total from deposited money
|
||||
meta:set_int("deposited", deposited - total)
|
||||
-- execute transaction
|
||||
core.chat_send_player(pname, "You purchased " .. tostring(product:get_count())
|
||||
.. " " .. product:get_description() .. " for " .. tostring(total) .. " MG.")
|
||||
give_product(sender, product)
|
||||
end
|
||||
|
||||
-- refresh formspec dialog
|
||||
meta:set_string("formspec", get_formspec(pos, sender))
|
||||
end,
|
||||
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
local deposited = calculate_value(stack)
|
||||
if deposited > 0 then
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_int("deposited", meta:get_int("deposited") + deposited)
|
||||
|
||||
-- refresh formspec dialog
|
||||
meta:set_string("formspec", get_formspec(pos, player))
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
-- load configured shops from world directory
|
||||
local shops_file = core.get_worldpath() .. "/server_shops.lua"
|
||||
local shops_file = core.get_worldpath() .. "/server_shops.json"
|
||||
local fopen = io.open(shops_file, "r")
|
||||
if fopen ~= nil then
|
||||
local content = fopen:read("*a")
|
||||
io.close(fopen)
|
||||
dofile(shops_file)
|
||||
|
||||
local json = core.parse_json(content)
|
||||
for _, shop in ipairs(json) do
|
||||
local sells = {}
|
||||
for k, v in pairs(shop.sells) do
|
||||
table.insert(sells, {k, v})
|
||||
end
|
||||
|
||||
-- FIXME: need safety checks
|
||||
server_shop.register_shop(shop.id, shop.name, sells)
|
||||
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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name = server_shop
|
||||
title = Server Shop
|
||||
description = Shops intended to be set up by server administrators.
|
||||
version = 1.0
|
||||
version = 1.1
|
||||
author = Jordan Irwin (AntumDeluge)
|
||||
depends = currency
|
||||
|
|
Binary file not shown.
|
@ -1,26 +0,0 @@
|
|||
# Blender v2.79 (sub 0) OBJ File: 'server_shop.blend'
|
||||
# www.blender.org
|
||||
mtllib server_shop.mtl
|
||||
o Cube
|
||||
v 0.500000 -0.500000 -0.500000
|
||||
v 0.500000 -0.500000 0.500000
|
||||
v -0.500000 -0.500000 0.500000
|
||||
v -0.500000 -0.500000 -0.500000
|
||||
v 0.500000 1.500000 -0.500000
|
||||
v 0.500000 1.500000 0.500000
|
||||
v -0.500000 1.500000 0.500000
|
||||
v -0.500000 1.500000 -0.500000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn -0.0000 -0.0000 1.0000
|
||||
vn -1.0000 -0.0000 -0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
usemtl Material
|
||||
s off
|
||||
f 1//1 2//1 3//1 4//1
|
||||
f 5//2 8//2 7//2 6//2
|
||||
f 1//3 5//3 6//3 2//3
|
||||
f 2//4 6//4 7//4 3//4
|
||||
f 3//5 7//5 8//5 4//5
|
||||
f 5//6 1//6 4//6 8//6
|
|
@ -0,0 +1,339 @@
|
|||
|
||||
local ss = server_shop
|
||||
|
||||
|
||||
local node_name = "server_shop:shop"
|
||||
|
||||
local currencies = {
|
||||
{"currency:minegeld", 1,},
|
||||
{"currency:minegeld_5", 5,},
|
||||
{"currency:minegeld_10", 10,},
|
||||
{"currency:minegeld_50", 50,},
|
||||
{"currency:minegeld_100", 100,},
|
||||
}
|
||||
|
||||
--- Calculates how much money is being deposited.
|
||||
local function calculate_value(stack)
|
||||
local value = 0
|
||||
for _, c in ipairs(currencies) do
|
||||
if stack:get_name() == c[1] then
|
||||
value = stack:get_count() * c[2]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local function get_shop_name(id)
|
||||
local shop = ss.get_shop(id)
|
||||
if shop then
|
||||
return shop.name
|
||||
end
|
||||
end
|
||||
|
||||
--- Retrieves item name/id from shop list.
|
||||
--
|
||||
-- @function get_shop_index
|
||||
-- @local
|
||||
-- @param shop_id String identifier of shop.
|
||||
-- @param idx Index of the item to retrieve.
|
||||
-- @return String item identifier or `nil` if shop does not contain item.
|
||||
local function get_shop_index(shop_id, idx)
|
||||
local shop = ss.get_shop(shop_id)
|
||||
if shop then
|
||||
local product = shop.def[idx]
|
||||
if product then
|
||||
return product[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the owner of the shop & gives admin privileges.
|
||||
--
|
||||
-- @local
|
||||
-- @function set_owner
|
||||
-- @param pos Position of shop.
|
||||
-- @param pname String name of new owner.
|
||||
local function set_owner(pos, pname)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("owner", pname)
|
||||
end
|
||||
|
||||
--- Calculates the price of item being purchased.
|
||||
--
|
||||
-- @function calculate_price
|
||||
-- @local
|
||||
-- @param shop_id String identifier of shop.
|
||||
-- @param item_id String identifier of item (e.g. default:dirt).
|
||||
-- @param quantity Number of item being purchased.
|
||||
-- @return Total value of purchase.
|
||||
local function calculate_price(shop_id, item_id, quantity)
|
||||
local shop = ss.get_shop(shop_id)
|
||||
if not shop then
|
||||
return 0
|
||||
end
|
||||
|
||||
local price_per = 0
|
||||
for _, i in ipairs(shop.def) do
|
||||
if i[1] == item_id then
|
||||
price_per = i[2]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return price_per * quantity
|
||||
end
|
||||
|
||||
--- Calculates money to be returned to player.
|
||||
--
|
||||
-- FIXME: not very intuitive
|
||||
local function calculate_refund(total)
|
||||
local refund = 0
|
||||
|
||||
local hun = math.floor(total / 100)
|
||||
total = total - (hun * 100)
|
||||
|
||||
local fif = math.floor(total / 50)
|
||||
total = total - (fif * 50)
|
||||
|
||||
local ten = math.floor(total / 10)
|
||||
total = total - (ten * 10)
|
||||
|
||||
local fiv = math.floor(total / 5)
|
||||
total = total - (fiv * 5)
|
||||
|
||||
-- at this point, 'total' should always be divisible by whole number
|
||||
local one = total / 1
|
||||
total = total - one
|
||||
|
||||
if total ~= 0 then
|
||||
core.log("warning", "Refund did not result in 0 deposited balance")
|
||||
end
|
||||
|
||||
local refund = {}
|
||||
for _, c in ipairs(currencies) do
|
||||
local iname = c[1]
|
||||
local ivalue = c[2]
|
||||
local icount = 0
|
||||
|
||||
if ivalue == 1 then
|
||||
icount = one
|
||||
elseif ivalue == 5 then
|
||||
icount = fiv
|
||||
elseif ivalue == 10 then
|
||||
icount = ten
|
||||
elseif ivalue == 50 then
|
||||
icount = fif
|
||||
elseif ivalue == 100 then
|
||||
icount = hun
|
||||
end
|
||||
|
||||
if icount > 0 then
|
||||
local stack = ItemStack(iname)
|
||||
stack:set_count(icount)
|
||||
table.insert(refund, stack)
|
||||
end
|
||||
end
|
||||
|
||||
return refund
|
||||
end
|
||||
|
||||
--- Add item(s) to player inventory or drops on ground.
|
||||
--
|
||||
-- @function player The player who is receiving the item.
|
||||
-- @local
|
||||
-- @param product String identifier of the item.
|
||||
-- @param quantity Amount to give.
|
||||
local function give_product(player, product, quantity)
|
||||
local istack = product
|
||||
if type(istack) == "string" then
|
||||
-- create the ItemStack
|
||||
istack = ItemStack(product)
|
||||
-- make sure we give at leaset 1
|
||||
if not quantity then quantity = 1 end
|
||||
istack:set_count(quantity)
|
||||
elseif quantity and istack:get_count() ~= quantity then
|
||||
istack:set_count(quantity)
|
||||
end
|
||||
|
||||
-- add to player inventory or drop on ground
|
||||
local pinv = player:get_inventory()
|
||||
if not pinv:room_for_item("main", istack) then
|
||||
core.chat_send_player(player:get_player_name(), "WARNING: "
|
||||
.. tostring(istack:get_count()) .. " " .. istack:get_description()
|
||||
.. " was dropped on the ground.")
|
||||
core.item_drop(istack, player, player:get_pos())
|
||||
else
|
||||
pinv:add_item("main", istack)
|
||||
end
|
||||
end
|
||||
|
||||
local function give_refund(meta, player)
|
||||
local refund = calculate_refund(meta:get_int("deposited"))
|
||||
for _, istack in ipairs(refund) do
|
||||
give_product(player, istack)
|
||||
end
|
||||
|
||||
-- reset deposited amount after refund
|
||||
meta:set_int("deposited", 0)
|
||||
end
|
||||
|
||||
|
||||
core.register_node(node_name, {
|
||||
description = "Shop",
|
||||
--drawtype = "nodebox",
|
||||
drawtype = "normal",
|
||||
tiles = {
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_side.png",
|
||||
"server_shop_front.png",
|
||||
"server_shop_side.png",
|
||||
},
|
||||
--[[
|
||||
drawtype = "mesh",
|
||||
mesh = "server_shop.obj",
|
||||
tiles = {"server_shop_mesh.png",},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.5, -0.5, -0.5, 0.5, 1.5, 0.5},
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.5, -0.5, -0.5, 0.5, 1.45, 0.5},
|
||||
},
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {-0.5, -0.5, -0.5, 0.5, 1.5, 0.5},
|
||||
},
|
||||
]]
|
||||
groups = {oddly_breakable_by_hand=1,},
|
||||
paramtype2 = "facedir",
|
||||
on_construct = function(pos)
|
||||
local meta = core.get_meta(pos)
|
||||
-- set which item should be selected when formspec is opened
|
||||
meta:set_int("default_selected", 1)
|
||||
meta:set_string("formspec", ss.get_formspec(pos))
|
||||
end,
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = core.get_meta(pos)
|
||||
set_owner(pos, placer:get_player_name())
|
||||
end,
|
||||
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("formspec", ss.get_formspec(pos, player))
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size("deposit", 1)
|
||||
end,
|
||||
can_dig = function(pos, player)
|
||||
local meta = core.get_meta(pos)
|
||||
local deposited = meta:get_int("deposited")
|
||||
|
||||
if deposited > 0 then
|
||||
core.log(player:get_player_name() .. " attempted to dig " .. node_name
|
||||
.. " containing " .. tostring(deposited) .. " MG at ("
|
||||
.. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
|
||||
return false
|
||||
end
|
||||
|
||||
return ss.is_shop_owner(pos, player) or ss.is_shop_admin(pos, player)
|
||||
end,
|
||||
on_dig = function(pos, node, digger)
|
||||
local deposited = core.get_meta(pos):get_int("deposited")
|
||||
core.node_dig(pos, node, digger)
|
||||
|
||||
if deposited < 0 then
|
||||
core.log("warning", digger:get_player_name() .. " dug " .. node_name
|
||||
.. " containing negative deposit balance")
|
||||
end
|
||||
end,
|
||||
on_receive_fields = function(pos, formname, fields, sender)
|
||||
local meta = core.get_meta(pos)
|
||||
local pname = sender:get_player_name()
|
||||
|
||||
if fields.quit then
|
||||
-- refund any money still deposited
|
||||
give_refund(meta, sender)
|
||||
-- reset selected to default when closed
|
||||
meta:set_int("selected", meta:get_int("default_selected"))
|
||||
elseif fields.btn_id and ss.is_shop_admin(pos, sender) then
|
||||
local new_id = fields.input_id:trim()
|
||||
-- FIXME: allow to be set to "" in order to remove shop
|
||||
if new_id ~= "" then
|
||||
core.log("action", pname .. " sets " .. node_name .. " ID to \"" .. new_id
|
||||
.. "\" at (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
|
||||
meta:set_string("id", new_id)
|
||||
fields.input_id = new_id
|
||||
|
||||
-- set or remove displayed text when pointed at
|
||||
local shop_name = get_shop_name(new_id)
|
||||
if shop_name then
|
||||
meta:set_string("infotext", shop_name)
|
||||
meta:set_string("name", shop_name)
|
||||
else
|
||||
meta:set_string("infotext", nil)
|
||||
meta:set_string("name", nil)
|
||||
end
|
||||
|
||||
-- make sure selected index is set back to default value
|
||||
meta:set_int("selected", meta:get_int("default_selected"))
|
||||
end
|
||||
elseif fields.products then
|
||||
-- set selected index in meta data to be retrieved when "buy" button is pressed
|
||||
meta:set_int("selected", fields.products:sub(5))
|
||||
elseif fields.btn_refund then
|
||||
give_refund(meta, sender)
|
||||
elseif fields.btn_buy then
|
||||
-- get selected index
|
||||
local selected = meta:get_int("selected")
|
||||
local shop_id = meta:get_string("id")
|
||||
local product = get_shop_index(shop_id, selected)
|
||||
|
||||
if not product then
|
||||
core.log("warning", "Trying to buy undefined item from shop \""
|
||||
.. tostring(shop_id) .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
-- FIXME: allow purchasing multiples
|
||||
local product_count = 1
|
||||
local total = calculate_price(shop_id, product, product_count)
|
||||
|
||||
local deposited = meta:get_int("deposited")
|
||||
if total > deposited then
|
||||
core.chat_send_player(pname, "You haven't deposited enough money.")
|
||||
return
|
||||
end
|
||||
|
||||
product = ItemStack(product)
|
||||
product:set_count(product_count)
|
||||
|
||||
-- subtract total from deposited money
|
||||
meta:set_int("deposited", deposited - total)
|
||||
-- execute transaction
|
||||
core.chat_send_player(pname, "You purchased " .. tostring(product:get_count())
|
||||
.. " " .. product:get_description() .. " for " .. tostring(total) .. " MG.")
|
||||
give_product(sender, product)
|
||||
end
|
||||
|
||||
-- refresh formspec dialog
|
||||
meta:set_string("formspec", ss.get_formspec(pos, sender))
|
||||
end,
|
||||
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
local deposited = calculate_value(stack)
|
||||
if deposited > 0 then
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_int("deposited", meta:get_int("deposited") + deposited)
|
||||
|
||||
-- refresh formspec dialog
|
||||
meta:set_string("formspec", ss.get_formspec(pos, player))
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
})
|
Loading…
Reference in New Issue