init repo
21
LICENSE.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright © 2021 Jordan Irwin (AntumDeluge)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
154
README.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
## Server Shops
|
||||
|
||||
### Description:
|
||||
|
||||
Shops intended to be set up by [Minetest](https://www.minetest.net/) server administrators.
|
||||
|
||||
No craft recipe is given as this for administrators, currently a shop can only be set up with the `/giveme` command. The two shop nodes are `server_shop:shop_small` & `server_shop:shop_large` (they function identically).
|
||||
|
||||

|
||||
|
||||
### Usage:
|
||||
|
||||
#### 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.
|
||||
|
||||
*Example:*
|
||||
```lua
|
||||
-- register seller
|
||||
server_shop.register_seller("frank", "Frank's Shop", {{"default:wood", 2}})
|
||||
|
||||
-- register buyer
|
||||
server_shop.register_buyer("julie", "Julie's Shop", {
|
||||
{"default:copper_lump", 5},
|
||||
{"default:iron_lump", 6},
|
||||
})
|
||||
```
|
||||
|
||||
#### 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:*
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type":"sell",
|
||||
"id":"frank",
|
||||
"name":"Frank's Shop",
|
||||
"products":[["default:wood",2]]
|
||||
},
|
||||
{
|
||||
"type":"buy",
|
||||
"id":"julie",
|
||||
"name":"Julie's Shop",
|
||||
"products":
|
||||
[
|
||||
["default:copper_lump",5],
|
||||
["default:iron_lump",6],
|
||||
]
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
#### 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`:
|
||||
```lua
|
||||
server_shop.register_currency("currency:minegeld", 1)
|
||||
server_shop.register_currency("currency:minegeld_5", 5)
|
||||
```
|
||||
|
||||
When registering new currencies in `server_shops.json`, set `type` to "currencies". `value` is a table of item names & worth:
|
||||
```json
|
||||
{
|
||||
"type":"currencies",
|
||||
"value":
|
||||
{
|
||||
"currency:minegeld":1,
|
||||
"currency:minegeld_5":5,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
You can also register a currency suffix to be displayed in the formspec. Simply set the string 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",
|
||||
},
|
||||
```
|
||||
|
||||
By default, if the [currency][mod.currency] mod is installed, the minegeld notes will be registered as currency. This can be disabled by setting `server_shop.use_currency_defaults` to `false` in `minetest.conf`.
|
||||
|
||||
#### Setting up Shops in Game:
|
||||
|
||||
Server admins use the chat command `/giveme server_shop:shop_small` or `/giveme server_shop:shop_large` 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 registered shop ID you want associated with this node ("frank" or "julie" for the examples above) & the list will be populated with the registered products & prices.
|
||||
|
||||
#### Using Seller Shops:
|
||||
|
||||
To make purchases, player first deposits registered currency items into the deposit slot. Select an item in the products list & press the "Buy" button. If there is adequate money deposited, player will receive the item & the cost will be deducted from the deposited amount. To retrieve any money not spent, press the "Refund" button. If the formspec is closed while there is still a deposit balance, the remaining money will be refunded back to the player. If there is not room in the player's inventory, the remaining balance will be dropped on the ground.
|
||||
|
||||
#### Using Buyer Shops:
|
||||
|
||||
For buyer shops, the product list shows what items can be sold to this shop & how much money a player will receive for each item. To sell to the shop, place an item in the deposit slot. The slot will only accept items that the owner will purchase. Press the "Sell" button to recieve the value of the item(s).
|
||||
|
||||
### Licensing:
|
||||
|
||||
- Code: [MIT](LICENSE.txt)
|
||||
- Textures: CC0
|
||||
|
||||
### Dependencies:
|
||||
|
||||
- Required:
|
||||
- simple_models
|
||||
- wdata
|
||||
- Optional:
|
||||
- [currency][mod.currency]
|
||||
- sounds
|
||||
|
||||
### Links:
|
||||
|
||||
- [](https://content.minetest.net/packages/AntumDeluge/server_shop/)
|
||||
- [GitHub repo](https://github.com/AntumMT/mod-server_shop)
|
||||
- [Forum](https://forum.minetest.net/viewtopic.php?t=26645)
|
||||
- [Reference](https://antummt.github.io/mod-server_shop/reference/latest/)
|
||||
- [Changelog](changelog.txt)
|
||||
- [TODO](TODO.txt)
|
||||
|
||||
|
||||
[mod.currency]: https://content.minetest.net/packages/VanessaE/currency/
|
||||
20
TODO.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
TODO:
|
||||
- Security:
|
||||
- might be an issue if admin changes shop ID while player is using
|
||||
- Functionality:
|
||||
- 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
|
||||
- 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)
|
||||
- allow shops to be configured live
|
||||
- register chat command "/server_shop" to register live
|
||||
- "/server_shop add <buyer>|<seller> <id> <item> <price>"
|
||||
- "/server_shop remove <buyer>|<seller> <id> <item>
|
||||
- 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
|
||||
108
api.lua
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
--- Server Shops API
|
||||
--
|
||||
-- @topic api.lua
|
||||
|
||||
|
||||
local ss = server_shop
|
||||
local S = core.get_translator(ss.modname)
|
||||
|
||||
local registered_currencies = {}
|
||||
ss.currency_suffix = nil
|
||||
|
||||
ss.currency_is_registered = function()
|
||||
for k, v in pairs(registered_currencies) do return true end
|
||||
return false
|
||||
end
|
||||
|
||||
ss.get_currencies = function()
|
||||
return table.copy(registered_currencies)
|
||||
end
|
||||
|
||||
ss.register_currency = function(item, value)
|
||||
if not core.registered_items[item] then
|
||||
ss.log("warning", "Registriere unbekanntes Item als Währung: " .. item)
|
||||
end
|
||||
value = tonumber(value)
|
||||
if not value or value <= 0 then
|
||||
ss.log("error", "Währungswert für " .. item .. " muss eine positive Zahl sein.")
|
||||
return
|
||||
end
|
||||
registered_currencies[item] = value
|
||||
ss.log("action", item .. " als Währung mit Wert " .. value .. " registriert.")
|
||||
end
|
||||
|
||||
if ss.use_currency_defaults then
|
||||
if not core.get_modpath("currency") then
|
||||
ss.log("warning", "Mod 'currency' nicht gefunden, Standardwährung wird nicht geladen.")
|
||||
else
|
||||
local all_currency = {
|
||||
{"currency:minegeld", 100}, {"currency:minegeld_5", 500},
|
||||
{"currency:minegeld_10", 1000}, {"currency:minegeld_50", 5000},
|
||||
{"currency:minegeld_100", 10000}, {"currency:minegeld_cent_5", 5},
|
||||
{"currency:minegeld_cent_10", 10}, {"currency:minegeld_cent_25", 25},
|
||||
}
|
||||
for _, c in ipairs(all_currency) do
|
||||
ss.register_currency(c[1], c[2])
|
||||
end
|
||||
ss.currency_suffix = "MG"
|
||||
end
|
||||
end
|
||||
|
||||
ss.get_shop = function(pos)
|
||||
if not pos then return nil end
|
||||
local meta = core.get_meta(pos)
|
||||
if meta:get_string("owner") == "" then return nil end
|
||||
|
||||
return {
|
||||
name = meta:get_string("name"),
|
||||
owner = meta:get_string("owner"),
|
||||
prices = core.deserialize(meta:get_string("prices")) or {},
|
||||
inv = meta:get_inventory(),
|
||||
}
|
||||
end
|
||||
|
||||
ss.update_infotext = function(pos)
|
||||
local shop = ss.get_shop(pos)
|
||||
if not shop then return end
|
||||
|
||||
local text = shop.name
|
||||
local inv_list = shop.inv:get_list("main")
|
||||
local inv_size = shop.inv:get_size("main")
|
||||
|
||||
for i=1, inv_size do
|
||||
local item = inv_list[i]
|
||||
local price_int = shop.prices[i]
|
||||
if not item:is_empty() and price_int and price_int > 0 then
|
||||
local count = item:get_count()
|
||||
local description = item:get_description()
|
||||
local price_str = string.format("%.2f", price_int / 100)
|
||||
|
||||
text = text .. "\n" .. count .. "x " .. description .. ": " .. price_str .. " " .. (ss.currency_suffix or "")
|
||||
end
|
||||
end
|
||||
|
||||
core.get_meta(pos):set_string("infotext", text)
|
||||
|
||||
local pos_top = {x=pos.x, y=pos.y + 1, z=pos.z}
|
||||
if core.get_node(pos_top).name == ss.modname..":shop_large_top" then
|
||||
core.get_meta(pos_top):set_string("infotext", text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
ss.is_shop_admin = function(player)
|
||||
if not player then return false end
|
||||
return core.check_player_privs(player, "server")
|
||||
end
|
||||
|
||||
ss.is_shop_owner = function(pos, player)
|
||||
if not player or not pos then
|
||||
return false
|
||||
end
|
||||
|
||||
local player_name = player:get_player_name()
|
||||
local meta = core.get_meta(pos)
|
||||
local owner_name = meta:get_string("owner")
|
||||
local is_owner = (player_name == owner_name)
|
||||
return is_owner
|
||||
end
|
||||
90
changelog.txt
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
v1.6.2
|
||||
------
|
||||
- updated for simple_models v2021-08-26
|
||||
|
||||
|
||||
v1.6.1
|
||||
------
|
||||
- updated for simple_models v2021-08-23
|
||||
|
||||
|
||||
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
|
||||
- added Spanish translation
|
||||
- added "tall" shop nodes
|
||||
- fixed buyer shop not being registered when currency & default mods not available
|
||||
- "buy" & "sell" functions are deteremined by shop "type" instead of separate registered nodes
|
||||
- default currencies not registered if currency mod not available
|
||||
- "get_currencies", "get_shops", & "get_shop" return table copy
|
||||
- added methods:
|
||||
- server_shop.unregister
|
||||
- server_shop.shop_type
|
||||
|
||||
|
||||
v1.4
|
||||
----
|
||||
- fixed custom suffix not reflected in product list & messages
|
||||
- custom quantity can be set
|
||||
- unregistered items are pruned from shops after server startup
|
||||
- fixed buyer shop not refunding on formspec close
|
||||
- added setting to disable auto-refunding on formspec close
|
||||
- currencies can be registered with any whole number value
|
||||
- "currencies" key in server_shops.json used to register multiple currencies
|
||||
- buyer shops now have "sell" button instead of automatically selling when item is dropped
|
||||
- added localization support
|
||||
- money is placed directly into player inventory when selling instead of using deposit medium
|
||||
|
||||
|
||||
v1.3
|
||||
----
|
||||
- added buyer shops
|
||||
- changed json product list type to indexed array
|
||||
|
||||
|
||||
v1.2
|
||||
----
|
||||
- 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
|
||||
|
||||
|
||||
v1.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
|
||||
|
||||
|
||||
v1.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 or node owners can set ID
|
||||
- players with "server" priv or node owners can dig
|
||||
- implemented deposit, purchase, & refund functionality
|
||||
0
command.lua
Normal file
80
deposit.lua
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
--- Server Shops Deposit Logics
|
||||
--
|
||||
-- @topic deposit.lua
|
||||
|
||||
local ss = server_shop
|
||||
local transaction = dofile(ss.modpath .. "/transaction.lua")
|
||||
|
||||
-- Einzahl-Slot für die Kassenreserve des Besitzers (unverändert)
|
||||
core.create_detached_inventory(ss.modname .. ":cash_deposit", {
|
||||
on_put = function(inv, listname, index, stack, player)
|
||||
local pmeta = player:get_meta()
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
if not pos then return stack:get_count() end
|
||||
|
||||
if ss.is_shop_owner(pos, player) or ss.is_shop_admin(player) then
|
||||
local value = transaction.calculate_currency_value(stack)
|
||||
if value > 0 then
|
||||
local shop_meta = core.get_meta(pos)
|
||||
shop_meta:set_int("cash_reserve", shop_meta:get_int("cash_reserve") + value)
|
||||
inv:set_stack(listname, index, nil)
|
||||
ss.show_formspec(pos, player)
|
||||
return 0
|
||||
end
|
||||
end
|
||||
return stack:get_count()
|
||||
end,
|
||||
}):set_size("main", 1)
|
||||
|
||||
|
||||
-- Einzahl-Slot für das Guthaben des Kunden (angepasst)
|
||||
core.create_detached_inventory(ss.modname .. ":customer_deposit", {
|
||||
on_put = function(inv, listname, index, stack, player)
|
||||
local pmeta = player:get_meta()
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
local stack_name = stack:get_name()
|
||||
|
||||
if stack_name == "bank_accounts:debit_card" or stack_name == "bank_accounts:credit_card" then
|
||||
local current_credit = pmeta:get_int(ss.modname .. ":session_credit") or 0
|
||||
if current_credit > 0 then
|
||||
local refund_stacks, remainder = transaction.calculate_refund(current_credit)
|
||||
for _, r_stack in ipairs(refund_stacks) do
|
||||
transaction.give_product(player, r_stack)
|
||||
end
|
||||
end
|
||||
|
||||
pmeta:set_string(ss.modname..":payment_method", stack_name)
|
||||
pmeta:set_int(ss.modname..":session_credit", 0)
|
||||
if pos then
|
||||
ss.show_formspec(pos, player)
|
||||
end
|
||||
return stack:get_count()
|
||||
end
|
||||
|
||||
local value = transaction.calculate_currency_value(stack)
|
||||
if value > 0 then
|
||||
pmeta:set_string(ss.modname..":payment_method", "cash")
|
||||
pmeta:set_int(ss.modname .. ":session_credit", (pmeta:get_int(ss.modname .. ":session_credit") or 0) + value)
|
||||
inv:set_stack(listname, index, nil)
|
||||
|
||||
if pos then
|
||||
ss.show_formspec(pos, player)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
return stack:get_count()
|
||||
end,
|
||||
on_take = function(inv, listname, index, stack, player)
|
||||
local pmeta = player:get_meta()
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
|
||||
if stack:get_name() == "bank_accounts:debit_card" or stack:get_name() == "bank_accounts:credit_card" then
|
||||
pmeta:set_string(ss.modname..":payment_method", nil)
|
||||
if pos then
|
||||
ss.show_formspec(pos, player)
|
||||
end
|
||||
end
|
||||
return stack
|
||||
end,
|
||||
}):set_size("main", 1)
|
||||
229
formspec.lua
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
--- Server Shops Formspec
|
||||
--
|
||||
-- @topic formspec
|
||||
|
||||
|
||||
local ss = server_shop
|
||||
local S = core.get_translator(ss.modname)
|
||||
local transaction = dofile(ss.modpath .. "/transaction.lua")
|
||||
|
||||
ss.get_formspec = function(pos, player)
|
||||
local shop = ss.get_shop(pos)
|
||||
if not shop then
|
||||
return "size[5,1]label[0,0;" .. S("Ungültiger Shop!") .. "]"
|
||||
end
|
||||
|
||||
local pmeta = player:get_meta()
|
||||
local is_owner = ss.is_shop_owner(pos, player) or ss.is_shop_admin(player)
|
||||
local current_tab = pmeta:get_string(ss.modname .. ":tab")
|
||||
if current_tab == "" and is_owner then current_tab = "inventory" end
|
||||
|
||||
local inv_size = shop.inv:get_size("main")
|
||||
|
||||
local fs_width = 14
|
||||
local fs_height = 12.5 -- Angepasst für 20 slots
|
||||
|
||||
local pos_fs = pos.x .. "," .. pos.y .. "," .. pos.z
|
||||
local formspec = "formspec_version[4]" .. "size[" .. fs_width .. "," .. fs_height .. "]" .. "label[0.5,0.4;" .. core.formspec_escape(shop.name) .. "]"
|
||||
|
||||
if is_owner then
|
||||
formspec = formspec .. "tabheader[0.2,0;tabs;" .. S("Inventar & Preise") .. "," .. S("Einstellungen") .. "," .. S("Kundenansicht") .. ";" .. (current_tab == "inventory" and "1" or current_tab == "settings" and "2" or "3") .. ";true;true]"
|
||||
|
||||
if current_tab == "inventory" then
|
||||
formspec = formspec .. "label[0.5,0.8;" .. S("Shop-Inventar") .. "]"
|
||||
.. "list[nodemeta:"..pos_fs..";main;0.5,1.2;5,1;0]"
|
||||
.. "list[nodemeta:"..pos_fs..";main;7.5,1.2;5,1;5]"
|
||||
for i=1, 5 do
|
||||
local x = 0.5 + (i-1) * 1.25; local price_int = shop.prices[i] or 0; local price_str = (price_int > 0) and string.format("%.2f", price_int / 100) or ""
|
||||
formspec = formspec .. "field["..x..",2.3;1.0,1;price_"..i..";;"..price_str.."]"
|
||||
end
|
||||
for i=6, 10 do
|
||||
local x = 7.5 + (i-6) * 1.25; local price_int = shop.prices[i] or 0; local price_str = (price_int > 0) and string.format("%.2f", price_int / 100) or ""
|
||||
formspec = formspec .. "field["..x..",2.3;1.0,1;price_"..i..";;"..price_str.."]"
|
||||
end
|
||||
|
||||
if inv_size > 10 then
|
||||
formspec = formspec .. "list[nodemeta:"..pos_fs..";main;0.5,3.7;5,1;10]"
|
||||
.. "list[nodemeta:"..pos_fs..";main;7.5,3.7;5,1;15]"
|
||||
for i=11, 15 do
|
||||
local x = 0.5 + (i-11) * 1.25; local price_int = shop.prices[i] or 0; local price_str = (price_int > 0) and string.format("%.2f", price_int / 100) or ""
|
||||
formspec = formspec .. "field["..x..",4.8;1.0,1;price_"..i..";;"..price_str.."]"
|
||||
end
|
||||
for i=16, 20 do
|
||||
local x = 7.5 + (i-16) * 1.25; local price_int = shop.prices[i] or 0; local price_str = (price_int > 0) and string.format("%.2f", price_int / 100) or ""
|
||||
formspec = formspec .. "field["..x..",4.8;1.0,1;price_"..i..";;"..price_str.."]"
|
||||
end
|
||||
end
|
||||
elseif current_tab == "settings" then
|
||||
formspec = formspec .. "label[0.5,0.8;" .. S("Shop-Name") .. "]" .. "field[0.5,1.2;7,0.7;shop_name;;" .. shop.name .. "]"
|
||||
end
|
||||
end
|
||||
|
||||
if not is_owner or current_tab == "customer" then
|
||||
formspec = formspec .. "label[0.5,0.8;"..S("Zum Kaufen auf einen Artikel klicken:").."]"
|
||||
local inv_list = shop.inv:get_list("main")
|
||||
for i=1, 10 do
|
||||
local item = inv_list[i]
|
||||
if not item:is_empty() and shop.prices[i] and shop.prices[i] > 0 then
|
||||
local is_row_1 = (i <= 5); local x = (is_row_1 and 0.5 or 7.5) + ((is_row_1 and i-1 or i-6) * 1.25)
|
||||
local y_image = 1.2; local y_price = y_image + 1.2; local y_currency = y_price + 0.3
|
||||
local count = item:get_count(); local price_str = string.format("%.2f", shop.prices[i] / 100)
|
||||
formspec = formspec .. "item_image_button["..x..","..y_image..";1,1;"..item:get_name()..";buy_slot_"..i..";]" .. "label["..x..","..y_image..";" .. count .. "]" .. "label["..x..","..y_price..";" .. price_str .. "]" .. "label["..x..","..y_currency..";" .. (ss.currency_suffix or "") .. "]"
|
||||
end
|
||||
end
|
||||
if inv_size > 10 then
|
||||
for i=11, 20 do
|
||||
local item = inv_list[i]
|
||||
if not item:is_empty() and shop.prices[i] and shop.prices[i] > 0 then
|
||||
local is_row_1 = (i <= 15); local x = (is_row_1 and 0.5 or 7.5) + ((is_row_1 and i-11 or i-16) * 1.25)
|
||||
local y_image = 3.7; local y_price = y_image + 1.2; local y_currency = y_price + 0.3
|
||||
local count = item:get_count(); local price_str = string.format("%.2f", shop.prices[i] / 100)
|
||||
formspec = formspec .. "item_image_button["..x..","..y_image..";1,1;"..item:get_name()..";buy_slot_"..i..";]" .. "label["..x..","..y_image..";" .. count .. "]" .. "label["..x..","..y_price..";" .. price_str .. "]" .. "label["..x..","..y_currency..";" .. (ss.currency_suffix or "") .. "]"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local player_inv_y = 7.5
|
||||
formspec = formspec .. "label[0.5," .. (player_inv_y - 0.4) .. ";" .. S("Dein Inventar") .. "]" .. "list[current_player;main;0.5," .. player_inv_y .. ";8,4;]"
|
||||
|
||||
local right_col_x = 10.8
|
||||
|
||||
if is_owner then
|
||||
if current_tab == "inventory" then
|
||||
local cash_reserve_int = core.get_meta(pos):get_int("cash_reserve"); local cash_reserve_str = string.format("%.2f", cash_reserve_int / 100)
|
||||
formspec = formspec .. "label["..right_col_x..", " .. (player_inv_y - 0.4) .. ";" .. S("Shop-Kasse:") .. "]"
|
||||
.. "label["..right_col_x..", " .. player_inv_y .. ";" .. cash_reserve_str .. " " .. (ss.currency_suffix or "") .. "]"
|
||||
.. "button["..right_col_x..", " .. (player_inv_y + 0.5) .. ";2.5,0.8;refund_reserve;"..S("Auszahlen").."]"
|
||||
end
|
||||
formspec = formspec .. "button["..right_col_x.."," .. (fs_height - 2) .. ";2.5,0.8;save_all;" .. S("Speichern") .. "]"
|
||||
end
|
||||
|
||||
if not is_owner or (is_owner and current_tab == "customer") then
|
||||
local payment_method = pmeta:get_string(ss.modname..":payment_method")
|
||||
local credit_str = "0.00"
|
||||
if bank_accounts and payment_method == "bank_accounts:debit_card" then
|
||||
local balance = bank_accounts.get_balance(player:get_player_name())
|
||||
credit_str = string.format("%.2f", balance)
|
||||
elseif bank_accounts and payment_method == "bank_accounts:credit_card" then
|
||||
local credit = bank_accounts.get_credit(player:get_player_name())
|
||||
credit_str = string.format("%.2f", credit)
|
||||
else
|
||||
local session_credit_int = pmeta:get_int(ss.modname..":session_credit") or 0
|
||||
credit_str = string.format("%.2f", session_credit_int / 100)
|
||||
end
|
||||
formspec = formspec .. "label["..right_col_x..", " .. (player_inv_y - 0.4) .. ";" .. S("Einzahlung (MG/Karte):") .. "]"
|
||||
.. "list[detached:"..ss.modname..":customer_deposit;main;"..right_col_x..","..player_inv_y..";1,1;]"
|
||||
.. "button["..right_col_x..",".. (player_inv_y + 1.2) ..";2.5,0.8;refund_session;"..S("Auszahlen").."]"
|
||||
.. "label["..right_col_x + 1.2 ..",".. (player_inv_y + 0.2) ..";" .. S("Guthaben:") .. "]"
|
||||
.. "label["..right_col_x + 1.2 ..",".. (player_inv_y + 0.6) ..";" .. credit_str .. " " .. (ss.currency_suffix or "") .. "]"
|
||||
end
|
||||
|
||||
formspec = formspec .. "button_exit[" .. right_col_x .. "," .. (fs_height - 1) .. ";2.5,0.8;close;" .. S("Schließen") .. "]"
|
||||
return formspec
|
||||
end
|
||||
|
||||
ss.show_formspec = function(pos, player)
|
||||
-- Die Reset-Logik wurde in node.lua -> on_rightclick verschoben
|
||||
core.show_formspec(player:get_player_name(), ss.modname..":shop", ss.get_formspec(pos, player))
|
||||
end
|
||||
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= ss.modname..":shop" then return end
|
||||
if fields.quit then
|
||||
local pmeta = player:get_meta()
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
if ss.refund_on_close and pos then
|
||||
-- Gilt für jeden Spieler mit Guthaben, auch den Besitzer
|
||||
local total = pmeta:get_int(ss.modname..":session_credit") or 0
|
||||
if total > 0 then
|
||||
local refund_stacks, remainder = transaction.calculate_refund(total)
|
||||
for _, stack in ipairs(refund_stacks) do transaction.give_product(player, stack) end
|
||||
pmeta:set_int(ss.modname..":session_credit", remainder)
|
||||
end
|
||||
local deposit_inv = core.get_inventory({type="detached", name=ss.modname..":customer_deposit"})
|
||||
if deposit_inv then
|
||||
local stack = deposit_inv:get_stack("main", 1)
|
||||
if not stack:is_empty() and (stack:get_name() == "bank_accounts:debit_card" or stack:get_name() == "bank_accounts:credit_card") then
|
||||
transaction.give_product(player, stack)
|
||||
deposit_inv:set_stack("main", 1, nil)
|
||||
pmeta:set_string(ss.modname..":payment_method", nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local pmeta = player:get_meta()
|
||||
local pos = core.deserialize(pmeta:get_string(ss.modname .. ":pos"))
|
||||
if not pos then return end
|
||||
|
||||
local is_owner = ss.is_shop_owner(pos, player) or ss.is_shop_admin(player)
|
||||
|
||||
-- KORREKTUR: inv_size wird jetzt nur einmal hier geholt, um für alle Schleifen gültig zu sein
|
||||
local inv = core.get_meta(pos):get_inventory()
|
||||
local inv_size = inv and inv:get_size("main") or 10
|
||||
|
||||
if is_owner and fields.tabs then
|
||||
local new_tab = "inventory"; if fields.tabs == "2" then new_tab = "settings" elseif fields.tabs == "3" then new_tab = "customer" end
|
||||
pmeta:set_string(ss.modname .. ":tab", new_tab)
|
||||
end
|
||||
|
||||
for i = 1, inv_size do
|
||||
if fields["buy_slot_"..i] then
|
||||
transaction.execute_purchase(pos, player, i)
|
||||
ss.show_formspec(pos, player)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if fields.refund_session then
|
||||
local total = pmeta:get_int(ss.modname..":session_credit") or 0
|
||||
if total > 0 then
|
||||
local refund_stacks, remainder = transaction.calculate_refund(total);
|
||||
for _, stack in ipairs(refund_stacks) do transaction.give_product(player, stack) end
|
||||
pmeta:set_int(ss.modname..":session_credit", remainder)
|
||||
end
|
||||
end
|
||||
|
||||
if is_owner then
|
||||
local shop_meta = core.get_meta(pos)
|
||||
if fields.save_all then
|
||||
local current_tab = pmeta:get_string(ss.modname .. ":tab") or "inventory"
|
||||
if current_tab == "settings" then
|
||||
if fields.shop_name then
|
||||
shop_meta:set_string("name", fields.shop_name or "")
|
||||
end
|
||||
elseif current_tab == "inventory" then
|
||||
local prices = core.deserialize(shop_meta:get_string("prices")) or {}
|
||||
local inv_list = inv:get_list("main")
|
||||
for i = 1, inv_size do
|
||||
if not inv_list[i]:is_empty() then
|
||||
if fields["price_"..i] then
|
||||
local price_input = fields["price_"..i]
|
||||
if price_input and price_input ~= "" then
|
||||
local price_num = tonumber((price_input:gsub(",", ".")))
|
||||
if price_num and price_num > 0 then
|
||||
prices[i] = math.floor(price_num * 100 + 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
prices[i] = nil
|
||||
end
|
||||
end
|
||||
shop_meta:set_string("prices", core.serialize(prices))
|
||||
end
|
||||
ss.update_infotext(pos)
|
||||
end
|
||||
if fields.refund_reserve then
|
||||
local total = shop_meta:get_int("cash_reserve")
|
||||
if total > 0 then
|
||||
local refund_stacks, remainder = transaction.calculate_refund(total)
|
||||
for _, stack in ipairs(refund_stacks) do transaction.give_product(player, stack) end
|
||||
shop_meta:set_int("cash_reserve", remainder)
|
||||
end
|
||||
end
|
||||
end
|
||||
ss.show_formspec(pos, player)
|
||||
end)
|
||||
30
init.lua
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
server_shop = {}
|
||||
local ss = server_shop
|
||||
|
||||
ss.modname = core.get_current_modname()
|
||||
ss.modpath = core.get_modpath(ss.modname)
|
||||
|
||||
-- Log-Funktion für saubere Nachrichten
|
||||
function ss.log(level, msg)
|
||||
if not msg then
|
||||
msg = level
|
||||
level = "action"
|
||||
end
|
||||
core.log(level, "[" .. ss.modname .. "] " .. msg)
|
||||
end
|
||||
|
||||
-- Definiert die Ladereihenfolge der einzelnen Skript-Dateien
|
||||
local scripts = {
|
||||
"settings",
|
||||
"api",
|
||||
"transaction",
|
||||
"deposit",
|
||||
"formspec",
|
||||
"node",
|
||||
}
|
||||
|
||||
for _, script in ipairs(scripts) do
|
||||
dofile(ss.modpath .. "/" .. script .. ".lua")
|
||||
end
|
||||
|
||||
ss.log("Mod '"..ss.modname.."' wurde erfolgreich geladen.")
|
||||
46
locale/server_shop.es.tr
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# textdomain:server_shop
|
||||
|
||||
# Translators: Jordan Irwin (AntumDeluge)
|
||||
|
||||
|
||||
Shop=Tienda
|
||||
Buy=Comprar
|
||||
Sell=Vender
|
||||
Refund=Reembolsar
|
||||
Close=Cerrar
|
||||
Set ID=Pon ID
|
||||
Deposited: @1=Depositado: @1
|
||||
Deposited: @1 @2=Depositado: @1 @2
|
||||
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
|
||||
46
locale/template.txt
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# textdomain:server_shop
|
||||
|
||||
# Translators:
|
||||
|
||||
|
||||
Shop=
|
||||
Buy=
|
||||
Sell=
|
||||
Refund=
|
||||
Close=
|
||||
Set ID=
|
||||
Deposited: @1=
|
||||
Deposited: @1 @2=
|
||||
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=
|
||||
9
mod.conf
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name = server_shop
|
||||
title = Server Shop
|
||||
description = Player-managed shops with limited inventory.
|
||||
version = 0.4
|
||||
author = Rage87
|
||||
min_minetest_version = 5.11
|
||||
depends = currency
|
||||
optional_depends = sounds, bank_accounts
|
||||
release = 1000
|
||||
121
node.lua
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
--- Server Shops Nodes
|
||||
--
|
||||
-- @topic nodes
|
||||
|
||||
local ss = server_shop
|
||||
local S = core.get_translator(ss.modname)
|
||||
|
||||
-- #################################################################
|
||||
-- ## GEMEINSAME FUNKTIONEN
|
||||
-- #################################################################
|
||||
|
||||
local function after_place_node(pos, placer, itemstack)
|
||||
local meta = core.get_meta(pos)
|
||||
if itemstack:get_name() == "server_shop:shop_large" then
|
||||
meta:get_inventory():set_size("main", 20)
|
||||
else
|
||||
meta:get_inventory():set_size("main", 10)
|
||||
end
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
meta:set_string("prices", core.serialize({}))
|
||||
meta:set_int("cash_reserve", 0)
|
||||
meta:set_string("name", S("@1's Shop", placer:get_player_name()))
|
||||
ss.update_infotext(pos)
|
||||
end
|
||||
|
||||
local function on_rightclick(pos, node, player, itemstack, pointed_thing)
|
||||
if core.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == ss.modname..":shop_large_bottom" then
|
||||
pos.y = pos.y - 1
|
||||
end
|
||||
|
||||
local pmeta = player:get_meta()
|
||||
pmeta:set_string(ss.modname .. ":pos", core.serialize(pos))
|
||||
|
||||
if not (ss.is_shop_owner(pos, player) or ss.is_shop_admin(player)) then
|
||||
pmeta:set_int(ss.modname..":session_credit", 0)
|
||||
end
|
||||
|
||||
ss.show_formspec(pos, player)
|
||||
end
|
||||
|
||||
local function can_dig(pos, player)
|
||||
local owner_pos = {x=pos.x, y=pos.y, z=pos.z}
|
||||
if core.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == ss.modname..":shop_large_bottom" then
|
||||
owner_pos.y = pos.y - 1
|
||||
end
|
||||
return ss.is_shop_owner(owner_pos, player) or ss.is_shop_admin(player)
|
||||
end
|
||||
|
||||
|
||||
-- #################################################################
|
||||
-- ## SHOP-DEFINITIONEN
|
||||
-- #################################################################
|
||||
|
||||
-- Kleiner Shop (1 Block)
|
||||
core.register_node(ss.modname..":shop_small", {
|
||||
description = S("Shop"),
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=1, oddly_breakable_by_hand=1},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
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",
|
||||
},
|
||||
after_place_node = after_place_node,
|
||||
on_rightclick = on_rightclick,
|
||||
can_dig = can_dig,
|
||||
})
|
||||
|
||||
-- Großer Shop, unterer Teil (enthält alle Daten)
|
||||
core.register_node(ss.modname..":shop_large_bottom", {
|
||||
description = S("Großer Shop"),
|
||||
paramtype2 = "facedir",
|
||||
drop = ss.modname..":shop_large_bottom",
|
||||
groups = {choppy=1, oddly_breakable_by_hand=1, not_in_creative_inventory=1},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
tiles = {
|
||||
"server_shop_side.png", "server_shop_side.png", "server_shop_side.png",
|
||||
"server_shop_side.png", "server_shop_side.png", "server_shop_front_large_bottom.png",
|
||||
},
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:get_inventory():set_size("main", 20)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
meta:set_string("prices", core.serialize({}))
|
||||
meta:set_int("cash_reserve", 0)
|
||||
meta:set_string("name", S("@1's Großer Shop", placer:get_player_name()))
|
||||
ss.update_infotext(pos)
|
||||
|
||||
local above = {x=pos.x, y=pos.y+1, z=pos.z}
|
||||
core.set_node(above, {name=ss.modname..":shop_large_top", param2=core.get_node(pos).param2})
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
local above = {x=pos.x, y=pos.y+1, z=pos.z}
|
||||
if core.get_node(above).name == ss.modname..":shop_large_top" then
|
||||
core.set_node(above, {name="air"})
|
||||
end
|
||||
end,
|
||||
on_rightclick = on_rightclick,
|
||||
can_dig = can_dig,
|
||||
})
|
||||
|
||||
-- Großer Shop, oberer Teil
|
||||
core.register_node(ss.modname..":shop_large_top", {
|
||||
description = S("Großer Shop"),
|
||||
paramtype2 = "facedir",
|
||||
diggable = false,
|
||||
drop = "",
|
||||
groups = {not_in_creative_inventory=1},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
tiles = {
|
||||
"server_shop_side.png", "server_shop_side.png", "server_shop_side.png",
|
||||
"server_shop_side.png", "server_shop_side.png", "server_shop_front_large_top.png",
|
||||
},
|
||||
-- KORREKTUR: on_destruct-Funktion hier komplett entfernt, um die Schleife zu verhindern.
|
||||
on_rightclick = on_rightclick,
|
||||
})
|
||||
|
||||
|
||||
-- Alias für den give-Befehl
|
||||
core.register_alias(ss.modname..":shop", ss.modname..":shop_small")
|
||||
core.register_alias(ss.modname..":shop_large", ss.modname..":shop_large_bottom")
|
||||
BIN
screenshot.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
7
settings.lua
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
--- Server Shops Settings
|
||||
--
|
||||
-- @topic settings.lua
|
||||
|
||||
-- Liest Einstellungen aus der minetest.conf oder verwendet Standardwerte
|
||||
server_shop.use_currency_defaults = core.settings:get_bool("server_shop.use_currency_defaults", true)
|
||||
server_shop.refund_on_close = core.settings:get_bool("server_shop.refund_on_close", true)
|
||||
6
settingtypes.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
# If currency mod is installed, automatically set up values for minegeld notes.
|
||||
server_shop.use_currency_defaults (Use currency mod defaults) bool true
|
||||
|
||||
# Refunds deposited money when formspec is closed.
|
||||
server_shop.refund_on_close (Refund on close) bool true
|
||||
BIN
textures/server_shop_front.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
textures/server_shop_front.xcf
Normal file
BIN
textures/server_shop_front_large.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
textures/server_shop_front_large_bottom.png
Normal file
|
After Width: | Height: | Size: 929 B |
BIN
textures/server_shop_front_large_top.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
textures/server_shop_front_small_new.xcf
Normal file
BIN
textures/server_shop_front_top.png
Normal file
|
After Width: | Height: | Size: 114 B |
BIN
textures/server_shop_mesh.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
textures/server_shop_side.png
Normal file
|
After Width: | Height: | Size: 114 B |
131
transaction.lua
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
--- Server Shops Transaction Logic
|
||||
--
|
||||
-- @topic transaction.lua
|
||||
|
||||
local ss = server_shop
|
||||
local S = core.get_translator(ss.modname)
|
||||
|
||||
local transaction = {}
|
||||
|
||||
function transaction.give_product(player, product)
|
||||
if not player or not product then return end
|
||||
local pinv = player:get_inventory()
|
||||
if not pinv:room_for_item("main", product) then
|
||||
core.chat_send_player(player:get_player_name(), S("WARNUNG: @1 @2 wurde auf den Boden fallen gelassen.", product:get_count(), product:get_description()))
|
||||
core.item_drop(product, player, player:get_pos())
|
||||
else
|
||||
pinv:add_item("main", product)
|
||||
end
|
||||
end
|
||||
|
||||
function transaction.calculate_refund(total)
|
||||
local currencies = ss.get_currencies()
|
||||
local keys = {}; for k in pairs(currencies) do table.insert(keys, k) end
|
||||
table.sort(keys, function(kL, kR) return currencies[kL] > currencies[kR] end)
|
||||
local refund = {}; local remain = total
|
||||
for _, k in ipairs(keys) do
|
||||
local v = currencies[k]
|
||||
if v > 0 then
|
||||
local count = math.floor(remain / v)
|
||||
if count > 0 then
|
||||
local stack = ItemStack(k); stack:set_count(count); table.insert(refund, stack)
|
||||
remain = remain - (count * v)
|
||||
end
|
||||
end
|
||||
end
|
||||
return refund, remain
|
||||
end
|
||||
|
||||
function transaction.calculate_currency_value(stack)
|
||||
local value = 0
|
||||
for c, v in pairs(ss.get_currencies()) do
|
||||
if stack:get_name() == c then
|
||||
return stack:get_count() * v
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function transaction.execute_purchase(pos, player, slot_index)
|
||||
local pmeta = player:get_meta()
|
||||
local player_inv = player:get_inventory()
|
||||
local shop = ss.get_shop(pos)
|
||||
|
||||
if not shop then return end
|
||||
|
||||
local quantity = 1
|
||||
|
||||
-- KORREKTUR: Die fehlerhafte `slot_index - 1` Logik wurde entfernt.
|
||||
-- Wir verwenden `slot_index` direkt, so wie es in der funktionierenden Version war.
|
||||
local item_to_buy = shop.inv:get_stack("main", slot_index)
|
||||
local price_per_item = shop.prices[slot_index]
|
||||
|
||||
if item_to_buy:is_empty() or not price_per_item or price_per_item <= 0 then
|
||||
core.chat_send_player(player:get_player_name(), S("Dieser Artikel ist nicht verfügbar."))
|
||||
return
|
||||
end
|
||||
|
||||
if item_to_buy:get_count() < quantity then
|
||||
core.chat_send_player(player:get_player_name(), S("Nicht genügend Artikel auf Lager."))
|
||||
return
|
||||
end
|
||||
|
||||
local purchase_stack = ItemStack(item_to_buy:get_name())
|
||||
purchase_stack:set_count(quantity)
|
||||
if not player_inv:room_for_item("main", purchase_stack) then
|
||||
core.chat_send_player(player:get_player_name(), S("Nicht genügend Platz im Inventar."))
|
||||
return
|
||||
end
|
||||
|
||||
local total_cost_cents = price_per_item * quantity
|
||||
local player_name = player:get_player_name()
|
||||
local payment_method = pmeta:get_string(ss.modname..":payment_method")
|
||||
|
||||
if payment_method == "bank_accounts:debit_card" or payment_method == "bank_accounts:credit_card" then
|
||||
local account_balance
|
||||
if payment_method == "bank_accounts:debit_card" then
|
||||
account_balance = bank_accounts.get_balance(player_name)
|
||||
else -- credit_card
|
||||
account_balance = bank_accounts.get_credit(player_name)
|
||||
end
|
||||
|
||||
local total_cost_float = total_cost_cents / 100
|
||||
|
||||
if account_balance < total_cost_float then
|
||||
core.chat_send_player(player_name, S("Dein Konto ist nicht ausreichend gedeckt."))
|
||||
return
|
||||
end
|
||||
|
||||
if payment_method == "bank_accounts:debit_card" then
|
||||
bank_accounts.add_balance(player_name, -total_cost_float, "Shop-Kauf", shop.name, shop.owner)
|
||||
else -- credit_card
|
||||
bank_accounts.add_credit(player_name, -total_cost_float, "Shop-Kauf", shop.name, shop.owner)
|
||||
end
|
||||
|
||||
else
|
||||
local credit = pmeta:get_int(ss.modname .. ":session_credit") or 0
|
||||
if credit < total_cost_cents then
|
||||
local needed = string.format("%.2f", (total_cost_cents - credit) / 100)
|
||||
core.chat_send_player(player_name, S("Du hast nicht genug Geld eingezahlt. (Es fehlen @1)", needed .. " " .. ss.currency_suffix))
|
||||
return
|
||||
end
|
||||
pmeta:set_int(ss.modname .. ":session_credit", credit - total_cost_cents)
|
||||
end
|
||||
|
||||
local shop_meta = core.get_meta(pos)
|
||||
local current_reserve = shop_meta:get_int("cash_reserve")
|
||||
shop_meta:set_int("cash_reserve", current_reserve + total_cost_cents)
|
||||
|
||||
item_to_buy:set_count(item_to_buy:get_count() - quantity)
|
||||
-- KORREKTUR: `set_stack` verwendet ebenfalls den direkten `slot_index`.
|
||||
shop.inv:set_stack("main", slot_index, item_to_buy)
|
||||
|
||||
transaction.give_product(player, purchase_stack)
|
||||
|
||||
local cost_str = string.format("%.2f", total_cost_cents / 100)
|
||||
core.chat_send_player(player_name, S("Du hast @1 @2 für @3 gekauft.", quantity, item_to_buy:get_description(), cost_str .. " " .. ss.currency_suffix))
|
||||
|
||||
ss.update_infotext(pos)
|
||||
end
|
||||
|
||||
return transaction
|
||||