Update server_shop mod to Git commit 993332e...
https://github.com/AntumMT/mod-server_shop/tree/993332e
This commit is contained in:
parent
6e3105b67a
commit
cfddf7bde4
@ -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-05-11*
|
||||
* [server_shop][] ([MIT][lic.server_shop]) -- version: [993332e Git][ver.server_shop] *2021-05-12*
|
||||
* [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*
|
||||
@ -564,7 +564,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
||||
[ver.pvp_areas]: https://github.com/everamzah/pvp_areas/tree/6e4d66d
|
||||
[ver.quartz]: https://github.com/minetest-mods/quartz/tree/72ec06f
|
||||
[ver.rainbow_ore]: https://github.com/FsxShader2012/rainbow_ore/tree/6e77693
|
||||
[ver.server_shop]: https://github.com/AntumMT/mod-server_shop/releases/tag/v1.1
|
||||
[ver.server_shop]: https://github.com/AntumMT/mod-server_shop/tree/993332e
|
||||
[ver.sfinv_buttons]: http://repo.or.cz/minetest_sfinv_buttons.git/tree/ebb1f7c
|
||||
[ver.shark]: https://github.com/AntumMT/mod-shark/tree/ef2507b
|
||||
[ver.sheep]: https://github.com/AntumMT/mod-cmer/tree/7c91aaf
|
||||
|
@ -1,4 +1,19 @@
|
||||
|
||||
pre
|
||||
- custom currencies can be registered
|
||||
- changed format of world "server_shops.json":
|
||||
- "sells" keyword changed to "products"
|
||||
- added required "type" keyword can be:
|
||||
- "sell" to register new seller shop
|
||||
- "currency" to register new currency
|
||||
- "suffix" to set a suffix to display after deposited amount
|
||||
- currencies can be registered using "currency" type:
|
||||
- subkeys are "name" (string) & "value" (number)
|
||||
- "currency" mod minegeld notes not registered automatically
|
||||
- displayed currency suffix can be customized or omitted
|
||||
- no longer uses node meta for formspec
|
||||
|
||||
|
||||
1.1
|
||||
- use json format for shops configuration in world directory
|
||||
- switched id & name parameters positions in register_shop
|
||||
|
@ -1,5 +1,6 @@
|
||||
## Server Shop
|
||||
|
||||
---
|
||||
### Description:
|
||||
|
||||
Shops intended to be set up by [Minetest](https://www.minetest.net/) server administrators.
|
||||
@ -10,12 +11,15 @@ No craft recipe is given as this for administrators, currently a machine can onl
|
||||
|
||||
![screenshot](screenshot.png)
|
||||
|
||||
#### Usage:
|
||||
---
|
||||
### Usage:
|
||||
|
||||
#### Registering Shops & Currencies:
|
||||
|
||||
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:
|
||||
```
|
||||
```lua
|
||||
server_shop.register_shop("basic", "Basic Shop", {
|
||||
{
|
||||
{"default:wood", 2},
|
||||
@ -24,20 +28,22 @@ server_shop.register_shop("basic", "Basic Shop", {
|
||||
})
|
||||
```
|
||||
|
||||
Shops can optionally be registered in `<world_path>/server_shops.json` file. Example:
|
||||
Shops can optionally be configured in `<world_path>/server_shops.json` file. To register a seller shop (buyers currently not supported), set `type` to "sell". `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 a list of products sold at the shop in format "name:value".
|
||||
|
||||
Example:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type":"sell",
|
||||
"id":"frank",
|
||||
"name":"Frank's Shop",
|
||||
"sells":
|
||||
{"default:wood":1}
|
||||
"products":{"default:wood":1}
|
||||
},
|
||||
{
|
||||
"type":"sell",
|
||||
"id":"julie",
|
||||
"name":"Julie's Shop",
|
||||
"sells":
|
||||
"products":
|
||||
{
|
||||
"default:iron_lump":5,
|
||||
"default:copper_lump":5,
|
||||
@ -46,30 +52,69 @@ Shops can optionally be registered in `<world_path>/server_shops.json` file. Exa
|
||||
]
|
||||
```
|
||||
|
||||
Currencies can be registered with `server_shop.register_currency`:
|
||||
```lua
|
||||
server_shop.register_currency("currency:minegeld", 1)
|
||||
server_shop.register_currency("currency:minegeld_5", 5)
|
||||
```
|
||||
|
||||
When registering a new currency in `server_shops.json`, set `type` to "currency". `name` is the item to be used as currency & `value` is the item's worth:
|
||||
```json
|
||||
{
|
||||
"type":"currency",
|
||||
"name":"currency:minegeld",
|
||||
"value":1,
|
||||
},
|
||||
{
|
||||
"type":"currency",
|
||||
"name":"currency:minegeld_5",
|
||||
"value":5,
|
||||
},
|
||||
```
|
||||
|
||||
You can also register a currency suffix to be displayed in the formspec. Simply set the value of `server_shop.currency_suffix`:
|
||||
```lua
|
||||
server_shop.currency_suffix = "MG"
|
||||
```
|
||||
|
||||
In `server_shops.json`, set `type` to "suffix" & `value` to the string to be displayed:
|
||||
```json
|
||||
{
|
||||
"type":"suffix",
|
||||
"value":"MG",
|
||||
},
|
||||
```
|
||||
|
||||
#### Setting up Shops in Game:
|
||||
|
||||
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.
|
||||
|
||||
To make purchases, players deposit [minegeld notes][mod.currency] into the deposit slot. Select an item to purchase & press the "Buy" button. If there is adequate money deposited, player will receive the item & the price will be deducted from the deposited amount. Press the "Refund" button to retrieve any money not spent.
|
||||
To make purchases, players deposit currency items into the deposit slot. Select an item to purchase & press the "Buy" button. If there is adequate money deposited, player will receive the item & the price will be deducted from the deposited amount. Press the "Refund" button to retrieve any money not spent.
|
||||
|
||||
***SECURITY WARNING:*** As stated, this mod is in early development. Currently, it is possible to interfere in another player's transactions. So this mod is *not* recommended for use with public servers at this time.
|
||||
|
||||
---
|
||||
### Licensing:
|
||||
|
||||
- Code: [MIT](LICENSE.txt)
|
||||
- Textures: CC0
|
||||
|
||||
---
|
||||
### Dependencies:
|
||||
|
||||
- Required:
|
||||
- [currency][mod.currency]
|
||||
- Optional:
|
||||
- none
|
||||
- Optional:
|
||||
- [currency][mod.currency]
|
||||
|
||||
Compatible with:
|
||||
|
||||
---
|
||||
### Links:
|
||||
|
||||
- [GitHub repo](https://github.com/AntumMT/mod-server_shop)
|
||||
- [Minetest forum](https://forum.minetest.net/viewtopic.php?t=26645)
|
||||
- [API](https://antummt.github.io/mod-server_shop/docs/api.html)
|
||||
- [Changelog](CHANGES.txt)
|
||||
- [TODO](TODO.txt)
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
|
||||
TODO:
|
||||
- Security:
|
||||
- don't allow other players to interfere with transactions
|
||||
- "Set ID" button is displayed to other players if admin makes changes while formspec visible
|
||||
- might be an issue if admin changes shop ID while player is using
|
||||
- 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
|
||||
- allow any currency value, not just 1, 5, 10, 50, & 100
|
||||
- Visuals:
|
||||
- touch up textures
|
||||
- fix texture tiling
|
||||
@ -14,10 +14,5 @@ TODO:
|
||||
- Misc.:
|
||||
- make usable with "folks" mod
|
||||
- add localization support
|
||||
- make "get_shop" global function
|
||||
- allow registering custom types of currency
|
||||
- clean up code
|
||||
- check if shop is already registered before re-registering
|
||||
- let shops with deposit balance be dug, & drop balance on ground
|
||||
- create shop that gives money for items
|
||||
- add chat command to reload shops file in world directory
|
||||
- set shop name from formspec instead of registration so shops with different names can use same content
|
||||
|
@ -1,13 +1,62 @@
|
||||
|
||||
--- API
|
||||
--
|
||||
-- @module api.lua
|
||||
|
||||
|
||||
local ss = server_shop
|
||||
|
||||
|
||||
--- Registered shops.
|
||||
--
|
||||
-- @local
|
||||
-- @table shops
|
||||
local shops = {}
|
||||
|
||||
--- Currencies registered for trade.
|
||||
--
|
||||
-- @table server_shop.registered_currencies
|
||||
ss.registered_currencies = {}
|
||||
|
||||
-- Suffix displayed after deposited amount.
|
||||
ss.currency_suffix = nil
|
||||
|
||||
--- Registers an item that can be used as currency.
|
||||
--
|
||||
-- TODO:
|
||||
-- - after registering currency, should re-organize table from highest value to lowest
|
||||
--
|
||||
-- @function server_shop.register_currency
|
||||
-- @tparam string item Item name.
|
||||
-- @tparam int value Value the item should represent.
|
||||
function ss.register_currency(item, value)
|
||||
if not core.registered_items[item] then
|
||||
ss.log("warning", "Registering unrecognized item as currency: " .. item)
|
||||
end
|
||||
|
||||
value = tonumber(value)
|
||||
if not value then
|
||||
ss.log("error", "Currency type for " .. item .. " must be a number. Got \"" .. type(value) .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
local old_value = ss.registered_currencies[item]
|
||||
if old_value then
|
||||
ss.log("warning", "Overwriting value for currency " .. item
|
||||
.. " from " .. tostring(old_value)
|
||||
.. " to " .. tostring(value))
|
||||
end
|
||||
|
||||
ss.registered_currencies[item] = value
|
||||
|
||||
ss.log("action", item .. " registered as currency with value of " .. tostring(value))
|
||||
end
|
||||
|
||||
--- Registers a shop list to be accessed via a shop node.
|
||||
--
|
||||
-- TODO:
|
||||
-- - log warning if `def` name or value missing or wrong type or
|
||||
-- if name is empty string
|
||||
--
|
||||
-- @function server_shop.register_shop
|
||||
-- @param id String ID associated with shop list.
|
||||
-- @param name Human readable name to be displayed.
|
||||
@ -34,16 +83,14 @@ end
|
||||
|
||||
--- Checks if a player has admin rights to for managing shop.
|
||||
--
|
||||
-- @function server_shoip.is_shop_admin
|
||||
-- @param pos Position of shop.
|
||||
-- @function server_shop.is_shop_admin
|
||||
-- @param player Player requesting permissions.
|
||||
-- @return `true` if player has *server* priv.
|
||||
function ss.is_shop_admin(pos, player)
|
||||
function ss.is_shop_admin(player)
|
||||
if not player then
|
||||
return false
|
||||
end
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
return core.check_player_privs(player, "server")
|
||||
end
|
||||
|
||||
|
50
mods/admin/server_shop/deposit.lua
Normal file
50
mods/admin/server_shop/deposit.lua
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
local ss = server_shop
|
||||
local transaction = dofile(ss.modpath .. "/transaction.lua")
|
||||
|
||||
|
||||
--- Calculates the value of an item stack.
|
||||
--
|
||||
-- @local
|
||||
-- @function server_shop.calculate_value
|
||||
-- @tparam ItemStack stack Item stack.
|
||||
local function calculate_value(stack)
|
||||
local value = 0
|
||||
|
||||
for c, v in pairs(ss.registered_currencies) do
|
||||
if stack:get_name() == c then
|
||||
value = stack:get_count() * v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
local callbacks = {
|
||||
allow_put = function(inv, listname, index, stack, player)
|
||||
-- TODO: move this to `on_put`
|
||||
|
||||
local pmeta = player:get_meta()
|
||||
|
||||
local to_deposit = calculate_value(stack)
|
||||
if to_deposit <= 0 then return 0 end
|
||||
|
||||
local id = pmeta:get_string(ss.modname .. ":id")
|
||||
if id:trim() == "" then return 0 end
|
||||
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
if not pos then return 0 end
|
||||
|
||||
transaction.set_deposit(id, player, transaction.get_deposit(id, player) + to_deposit)
|
||||
|
||||
-- refresh formspec dialog
|
||||
ss.show_formspec(pos, player)
|
||||
|
||||
return -1
|
||||
end,
|
||||
}
|
||||
|
||||
local inv = core.create_detached_inventory(ss.modname, callbacks)
|
||||
inv:set_size("deposit", 1)
|
@ -7,13 +7,43 @@ local fs_height = 11
|
||||
local btn_w = 1.75
|
||||
local btn_y = 4.6
|
||||
|
||||
--- Retrieves shop name by ID.
|
||||
--
|
||||
-- @local
|
||||
-- @function get_shop_name
|
||||
-- @tparam string id String identifier of shop.
|
||||
-- @treturn string Shop's name representation.
|
||||
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.
|
||||
--
|
||||
-- @local
|
||||
-- @function get_shop_index
|
||||
-- @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
|
||||
|
||||
--- Retrieves shop product list by ID.
|
||||
--
|
||||
-- @function get_product_list
|
||||
-- @local
|
||||
-- @function get_product_list
|
||||
-- @param id String identifier of shop.
|
||||
-- @return String of shop contents.
|
||||
function get_product_list(id)
|
||||
local function get_product_list(id)
|
||||
local products = ""
|
||||
local shop = ss.get_shop(id)
|
||||
|
||||
@ -53,29 +83,37 @@ function get_product_list(id)
|
||||
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")
|
||||
--- Retrieves formspec layout.
|
||||
--
|
||||
-- @function server_shop.get_formspec
|
||||
-- @param pos
|
||||
-- @param player
|
||||
function ss.get_formspec(pos, player)
|
||||
local smeta = core.get_meta(pos)
|
||||
local pmeta = player:get_meta()
|
||||
local id = smeta:get_string("id")
|
||||
local deposited = pmeta:get_int(ss.modname .. ":" .. id .. ":deposited")
|
||||
|
||||
local formspec = "formspec_version[4]size[" .. tostring(fs_width) .. "," .. tostring(fs_height) .."]"
|
||||
|
||||
local shop_name = meta:get_string("name"):trim()
|
||||
local shop_name = smeta: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
|
||||
if ss.is_shop_admin(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"))
|
||||
local selected_idx = 1
|
||||
if player then
|
||||
selected_idx = pmeta:get_int(ss.modname .. ":selected")
|
||||
end
|
||||
if selected_idx < 1 then
|
||||
selected_idx = 1
|
||||
end
|
||||
|
||||
-- get item name for displaying image
|
||||
@ -84,12 +122,11 @@ function server_shop.get_formspec(pos, player)
|
||||
|
||||
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
|
||||
if selected_idx > #shop.def then
|
||||
selected_idx = #shop.def
|
||||
end
|
||||
|
||||
selected_item = shop.def[meta:get_int("selected")]
|
||||
selected_item = shop.def[selected_idx]
|
||||
if selected_item then
|
||||
selected_item = selected_item[1]
|
||||
end
|
||||
@ -98,10 +135,18 @@ function server_shop.get_formspec(pos, player)
|
||||
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]"
|
||||
.. "label[0.2,1;Deposited: " .. tostring(deposited)
|
||||
if ss.currency_suffix and ss.currency_suffix ~= "" then
|
||||
formspec = formspec .. " " .. ss.currency_suffix
|
||||
end
|
||||
formspec = formspec .. "]"
|
||||
|
||||
if id ~= "" then -- don't allow deposits to unregistered stores
|
||||
formspec = formspec .. "list[detached:" .. ss.modname .. ";deposit;0.2,1.5;1,1;0]"
|
||||
end
|
||||
|
||||
formspec = formspec .. "textlist[2.15,1.5;9.75,3;products;" .. get_product_list(id) .. ";"
|
||||
.. tostring(selected_idx) .. ";false]"
|
||||
|
||||
if selected_item and selected_item ~= "" then
|
||||
formspec = formspec
|
||||
@ -120,3 +165,110 @@ function server_shop.get_formspec(pos, player)
|
||||
|
||||
return formspec .. formname
|
||||
end
|
||||
|
||||
--- Displays formspec to player.
|
||||
--
|
||||
-- @function server_shop.show_formspec
|
||||
-- @param pos
|
||||
-- @param player
|
||||
function ss.show_formspec(pos, player)
|
||||
core.show_formspec(player:get_player_name(), ss.modname .. ":shop", ss.get_formspec(pos, player))
|
||||
end
|
||||
|
||||
|
||||
local transaction = dofile(ss.modpath .. "/transaction.lua")
|
||||
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname == ss.modname .. ":shop" then
|
||||
local pmeta = player:get_meta()
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
if not pos then
|
||||
ss.log("error", "cannot retrieve shop position from player meta data")
|
||||
return false
|
||||
end
|
||||
|
||||
local smeta = core.get_meta(pos)
|
||||
local id = smeta:get_string("id")
|
||||
|
||||
if fields.quit then
|
||||
-- return money to player if formspec closed
|
||||
transaction.give_refund(id, player)
|
||||
return false
|
||||
elseif fields.btn_id or (fields.key_enter and fields.key_enter_field == "input_id") then
|
||||
-- safety check that only server admin can set ID
|
||||
if not ss.is_shop_admin(player) then
|
||||
ss.log("warning", "non-admin player " .. player.get_player_name()
|
||||
.. " attempted to change shop ID at ("
|
||||
.. tostring(pos.x) .. "," .. tostring(pos.y) .. "," .. tostring(pos.z)
|
||||
.. ")")
|
||||
return false
|
||||
end
|
||||
|
||||
-- should not happen
|
||||
if not fields.input_id then
|
||||
ss.log("error", "Cannot retrieve ID input")
|
||||
return false
|
||||
end
|
||||
|
||||
fields.input_id = fields.input_id:trim()
|
||||
smeta:set_string("id", fields.input_id)
|
||||
|
||||
-- set or remove displayed text when pointed at
|
||||
local shop_name = get_shop_name(fields.input_id)
|
||||
if shop_name then
|
||||
smeta:set_string("infotext", shop_name)
|
||||
smeta:set_string("name", shop_name)
|
||||
else
|
||||
smeta:set_string("infotext", nil)
|
||||
smeta:set_string("name", nil)
|
||||
end
|
||||
|
||||
ss.log("action", "server admin " .. player:get_player_name()
|
||||
.. " set shop ID at ("
|
||||
.. tostring(pos.x) .. "," .. tostring(pos.y) .. "," .. tostring(pos.z)
|
||||
.. ") to \"" .. id .. "\"")
|
||||
elseif fields.products then
|
||||
local idx = tonumber(fields.products:sub(5))
|
||||
if type(idx) == "number" then
|
||||
pmeta:set_int(ss.modname .. ":selected", idx)
|
||||
end
|
||||
elseif fields.btn_refund then
|
||||
transaction.give_refund(id, player)
|
||||
elseif fields.btn_buy then
|
||||
local idx = pmeta:get_int(ss.modname .. ":selected")
|
||||
local product = get_shop_index(id, idx)
|
||||
|
||||
if not product then
|
||||
ss.log("warning", "Trying to buy undefined item from shop \""
|
||||
.. tostring(id) .. "\"")
|
||||
return false
|
||||
end
|
||||
|
||||
-- FIXME: allow purchasing multiples
|
||||
local product_count = 1
|
||||
local total = transaction.calculate_price(id, product, product_count)
|
||||
|
||||
local deposited = transaction.get_deposit(id, player)
|
||||
if total > deposited then
|
||||
core.chat_send_player(player:get_player_name(), "You haven't deposited enough money.")
|
||||
return false
|
||||
end
|
||||
|
||||
product = ItemStack(product)
|
||||
product:set_count(product_count)
|
||||
|
||||
-- subtract total from deposited money
|
||||
transaction.set_deposit(id, player, deposited - total)
|
||||
|
||||
-- execute transaction
|
||||
core.chat_send_player(player:get_player_name(), "You purchased " .. tostring(product:get_count())
|
||||
.. " " .. product:get_description() .. " for " .. tostring(total) .. " MG.")
|
||||
transaction.give_product(player, product, product_count)
|
||||
end
|
||||
|
||||
-- refresh formspec view
|
||||
ss.show_formspec(pos, player)
|
||||
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
@ -1,34 +1,41 @@
|
||||
|
||||
server_shop = {}
|
||||
server_shop.modname = core.get_current_modname()
|
||||
server_shop.modpath = core.get_modpath(server_shop.modname)
|
||||
local ss = server_shop
|
||||
|
||||
function server_shop.log(lvl, msg)
|
||||
ss.modname = core.get_current_modname()
|
||||
ss.modpath = core.get_modpath(ss.modname)
|
||||
|
||||
function ss.log(lvl, msg)
|
||||
if not msg then
|
||||
msg = lvl
|
||||
lvl = nil
|
||||
end
|
||||
|
||||
if not lvl then
|
||||
core.log("[" .. server_shop.modname .. "] " .. msg)
|
||||
core.log("[" .. ss.modname .. "] " .. msg)
|
||||
else
|
||||
core.log(lvl, "[" .. server_shop.modname .. "] " .. msg)
|
||||
core.log(lvl, "[" .. ss.modname .. "] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
local scripts = {
|
||||
"api",
|
||||
"deposit",
|
||||
"formspec",
|
||||
"node",
|
||||
}
|
||||
|
||||
for _, script in ipairs(scripts) do
|
||||
dofile(server_shop.modpath .. "/" .. script .. ".lua")
|
||||
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")
|
||||
@ -36,13 +43,52 @@ if fopen ~= nil then
|
||||
|
||||
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
|
||||
if shop.type == "currency" then
|
||||
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
|
||||
|
||||
-- FIXME: need safety checks
|
||||
server_shop.register_shop(shop.id, shop.name, sells)
|
||||
ss.register_currency(shop.name, shop.value)
|
||||
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" 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
|
||||
shop.id = shop.id:trim()
|
||||
shop.name = shop.name:trim()
|
||||
local products = {}
|
||||
|
||||
for k, v in pairs(shop.products) do
|
||||
if type(k) ~= "string" or k == "" then
|
||||
shop_file_error("shop " .. shop.id .. ": invalid or undeclared product name, must be string")
|
||||
elseif type(v) ~= "number" or v <= 0 then
|
||||
shop_file_error("shop " .. shop.id .. ": invalid or undeclared product value ("
|
||||
.. k .. "), must be number greater than 0")
|
||||
else
|
||||
table.insert(products, {k, v})
|
||||
end
|
||||
end
|
||||
|
||||
if #products == 0 then
|
||||
ss.log("warning", shop_file .. ": empty shop list for shop id \"" .. shop.id .. "\"")
|
||||
end
|
||||
|
||||
server_shop.register_shop(shop.id, shop.name, products)
|
||||
end
|
||||
elseif not shop.type then
|
||||
error(shops_file .. ": mandatory \"type\" parameter not set")
|
||||
else
|
||||
error(shops_file .. ": Unrecognized type: " .. shop.type)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- create file if doesn't exist
|
||||
|
@ -3,4 +3,4 @@ title = Server Shop
|
||||
description = Shops intended to be set up by server administrators.
|
||||
version = 1.1
|
||||
author = Jordan Irwin (AntumDeluge)
|
||||
depends = currency
|
||||
optional_depends = currency
|
||||
|
@ -2,185 +2,7 @@
|
||||
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, {
|
||||
core.register_node(ss.modname .. ":shop", {
|
||||
description = "Shop",
|
||||
--drawtype = "nodebox",
|
||||
drawtype = "normal",
|
||||
@ -212,128 +34,21 @@ core.register_node(node_name, {
|
||||
]]
|
||||
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())
|
||||
-- set node owner
|
||||
core.get_meta(pos):set_string("owner", 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)
|
||||
local pmeta = player:get_meta()
|
||||
|
||||
-- store node pos in player meta for retrieval in callbacks
|
||||
pmeta:set_string(ss.modname .. ":pos", core.serialize(pos))
|
||||
-- store selected index in player meta for retrieval in callbacks
|
||||
pmeta:set_int(ss.modname .. ":selected", 1)
|
||||
|
||||
ss.show_formspec(pos, player)
|
||||
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)
|
||||
return ss.is_shop_owner(pos, player) or ss.is_shop_admin(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
|
||||
})
|
||||
|
171
mods/admin/server_shop/transaction.lua
Normal file
171
mods/admin/server_shop/transaction.lua
Normal file
@ -0,0 +1,171 @@
|
||||
|
||||
local ss = server_shop
|
||||
|
||||
|
||||
--- Sets deposited amount for shop.
|
||||
--
|
||||
-- @local
|
||||
-- @function set_deposit
|
||||
-- @tparam string id Shop id.
|
||||
-- @param player Player for whom deposit is being set.
|
||||
-- @tparam int amount The amount deposit should be set to.
|
||||
local function set_deposit(id, player, amount)
|
||||
local pmeta = player:get_meta()
|
||||
local deposit_id = ss.modname .. ":" .. id .. ":deposited"
|
||||
|
||||
pmeta:set_int(deposit_id, amount)
|
||||
end
|
||||
|
||||
--- Retrieves amount player has deposited at shop.
|
||||
--
|
||||
-- @local
|
||||
-- @function get_deposit
|
||||
-- @tparam string id Shop id.
|
||||
-- @param player Player to check.
|
||||
-- @treturn int Total amount currently deposited.
|
||||
local function get_deposit(id, player)
|
||||
return player:get_meta():get_int(ss.modname .. ":" .. id .. ":deposited")
|
||||
end
|
||||
|
||||
--- Add item(s) to player inventory or drops on ground.
|
||||
--
|
||||
-- @local
|
||||
-- @function player The player who is receiving the item.
|
||||
-- @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
|
||||
|
||||
--- Calculates money to be returned to player.
|
||||
--
|
||||
-- FIXME:
|
||||
-- - not very intuitive
|
||||
-- - doesn't allow currency values other than 1, 5, 10, 50, & 100
|
||||
--
|
||||
-- @local
|
||||
-- @function calculate_refund
|
||||
-- @param total
|
||||
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, v in pairs(ss.registered_currencies) do
|
||||
local icount = 0
|
||||
|
||||
if v == 1 then
|
||||
icount = one
|
||||
elseif v == 5 then
|
||||
icount = fiv
|
||||
elseif v == 10 then
|
||||
icount = ten
|
||||
elseif v == 50 then
|
||||
icount = fif
|
||||
elseif v == 100 then
|
||||
icount = hun
|
||||
end
|
||||
|
||||
if icount > 0 then
|
||||
local stack = ItemStack(c)
|
||||
stack:set_count(icount)
|
||||
table.insert(refund, stack)
|
||||
end
|
||||
end
|
||||
|
||||
return refund
|
||||
end
|
||||
|
||||
--- Returns remaining deposited money to player.
|
||||
--
|
||||
-- @local
|
||||
-- @function give_refund
|
||||
-- @tparam string id Shop id.
|
||||
-- @param player Player to whom refund is given.
|
||||
local function give_refund(id, player)
|
||||
local pmeta = player:get_meta()
|
||||
local deposit_id = ss.modname .. ":" .. id .. ":deposited"
|
||||
|
||||
local refund = calculate_refund(pmeta:get_int(deposit_id))
|
||||
for _, istack in ipairs(refund) do
|
||||
give_product(player, istack)
|
||||
end
|
||||
|
||||
-- reset deposited amount after refund
|
||||
pmeta:set_string(deposit_id, nil)
|
||||
end
|
||||
|
||||
--- Calculates the price of item being purchased.
|
||||
--
|
||||
-- FIXME: might be broken after shop def change
|
||||
--
|
||||
-- @local
|
||||
-- @function calculate_price
|
||||
-- @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
|
||||
|
||||
|
||||
return {
|
||||
set_deposit = set_deposit,
|
||||
get_deposit = get_deposit,
|
||||
give_product = give_product,
|
||||
give_refund = give_refund,
|
||||
calculate_price = calculate_price,
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user