added new machines

This commit is contained in:
Rainer 2025-08-22 02:10:35 +02:00
parent ceb168de55
commit 8603834f56
35 changed files with 2223 additions and 1204 deletions

190
README.md
View file

@ -1,146 +1,68 @@
# Bank Accounts (Redo & Extended)
# bank_accounts
This mod adds an ATM, card swipe, debit and credit cards to Minetest.
A complete overhaul and extension of the original `bank_accounts` mod by Tmanyo (Trent Lasich). This project has been restructured from the ground up to ensure stability, security, and a significantly expanded feature set.
# Important
To get an ATM, you must have the give priv. Type "/giveme bank_accounts:atm". I designed it this way to prevent buildings from being built out of ATMs. I want the mod to be realistic, and buildings made out of ATMs are not realistic.
## About This Version
If you have privs priv. When you grant yourself all, you will be given "seized" priv. Revoke it, else your account will be seized.
This version is a "Redo" of the original code. The focus was on fixing critical bugs, preventing exploits, and drastically improving stability, especially in multiplayer environments. Furthermore, numerous new features such as an automatic interest system, a detailed account statement, and a wire transfer machine have been added to provide a comprehensive in-game economic system.
Credits:
- NathanS21 for textures and models.
- Tmanyo for code.
## Features Overview
This mod uses the currency mod to provide different ways of paying for items in Minetest.
- Debit card
- Credit Card
* **Complete Account System:** Players automatically receive an account with a balance and a credit line.
* **5 Interactive Machines:** ATM, Teller Computer, PIN Terminal, Card Swipe, and Wire Transfer Machine (WTM).
* **Detailed Account Statement:** All transactions are logged and can be viewed via the ATM or Teller Computer.
* **Automatic Interest System:** A performant background process calculates daily interest on balances and credit debts.
* **Secure Transactions:** All actions are secured against exploits (e.g., duplication bugs) and invalid inputs.
* **API for Other Mods:** Allows other mods to safely interact with the banking system.
* **Fully Translatable (i18n):** All texts can be easily adapted for other languages.
All Items:
- ATM (Automatic Teller Machine)
- Card-Swipe
- ATM Card
- Debit Card
- Credit Card
## Dependencies
Chatcommands:
- All players:
- /set_pin #### (Set your Personal Identification Number)
- Admins:
- /account_balance {name} {number} | (Set a player's account balance)
- /wipe {name} | (Clear a player's bank account)
- /forgive {name} | (Clear a player's credit debt)
- /add {name} {number} | (Add to a player's account balance)
- /subtract {name} {number} | (Subtract from a player's account balance)
- /seize {name} | (Seize a player's account)
- /unseize {name} | (Unseize a player's account)
This mod requires the **Currency Mod** to function.
* **ContentDB Link:** [https://content.luanti.org/packages/mt-mods/currency/](https://content.luanti.org/packages/mt-mods/currency/)
Privileges:
- bank_teller (Allows players to use a Bank Teller's Computer. You should only give this to trusted individuals.)
- unseized (If you have this, your account is not seized.)
## Known Issues & WIP
With a debit card, when you buy an item through a card-swipe, it automatically takes money that you have deposited out of your account.
With a credit card, when you buy an item through a card-swipe, it builds up credit debt. There is a recommended monthly credit payment.
If you don't pay the payment, version 1.0 of this mod will not penalize you. It is more incentive to pay off your credit debt. Admins
can look at your credit debt and determine what they want to do. Either way, the person you bought goods from gets their money. Admins
can seize your ability to buy items with credit.
This version is significantly more stable than the original mod, especially on multiplayer servers. Most critical multiplayer bugs and data loss issues have been resolved.
To view your account statistics, you can find an ATM or Automatic Teller Machine. If you don't already have a PIN (Personal Identification
Number), you can get one by using /set_pin ####. Replace # with a number. I don't recommend using a number that is important to you because
the admins can see it. Once you set your PIN, you are given an ATM card which allows you to access your account. Right click the ATM with
your ATM card and enter your PIN. You can then see your account balance, total credit debt, and your monthly credit payment. If you want
to raise your account balance, you can click deposit and place currency in the correct box for each bill. It will automatically add up
and be added to your balance. You can't cheat and place other objects in the boxes, that is insured not to happen. If you want to withdraw
money from your account, click withdraw and enter the amount. Don't worry you can't recieve a larger amount of money than is in your account.
You can pay credit debt off by clicking the "Pay Monthly Credit Payment" button. You have an option to pay the monthly amount or more.
You can get a credit and debit card by clicking on each button on the home screen of the ATM. Just because you may have 20 of each card
doesn't make a difference. You are not any more wealthy than you are with just one of each.
However, there is a known theoretical race condition that can occur under very specific circumstances:
* **If two different players interact with two different machines (e.g., two ATMs) at the exact same moment, it is possible for a transaction to be misdirected to the wrong machine.**
When using a card-swipe, the owner is the seller. The owner must right click the node first and enter a price for the item(s). Then the
owner needs to place the item(s) within the top 8 set of boxes and click enter. The buyer then right clicks the node and removes the item(s)
from the top 8 boxes and places them in their own inventory and then clicks enter. If you are trying to use a debit card and it won't let
you see the screen, that means you do not have enough money on your account. At this point, you can use your credit card, but don't forget
to pay it off.
The time window for this to occur is extremely small (typically less than a few seconds between the two players' clicks), making it a very rare event on most servers.
You can view use of the chatcommands by viewing the chatcommands.lua file.
This mod is under development still, so there may be some issues.
You can reach the mod creator on irc.inchra.net - #RRHMS-DownDeep or Freenode irc - #minetest.
This is a known limitation resulting from a workaround needed to ensure stability across different Minetest clients. It is considered a work-in-progress (WIP) issue that may be addressed in future updates.
## Getting Started (For Players)
To use the banking system, you first need to set your personal PIN.
1. Find a **PIN Terminal** (a small, wall-mounted device).
2. Right-click it. As a new customer, you will be prompted to set a 4-digit PIN.
3. After successful entry, you will automatically receive your **Bank Card** (`ATM Card`) in your inventory. You will need this card to access most of the other machines.
## The Machines (Nodes) in Detail
### PIN Terminal
* **Purpose:** The primary point of contact for new players and for PIN management.
* **Functions:**
* **New Players:** Are prompted to set their first, personal 4-digit PIN. They then receive their first bank card.
* **Existing Players:** Can change their PIN (requires entering the old PIN for security) or request a new bank card if the old one was lost.
### ATM (Automatic Teller Machine)
* **Purpose:** Standard interactions with one's own account.
* **Operation:** Requires a Bank Card (`ATM Card`) and PIN entry.
* **Functions:**
* **Deposit:** Deposit cash (`currency` items).
* **Withdrawal:** Withdraw cash (integers only).
* **Pay Credit:** Provides the option to pay the due rate or a custom amount of the credit debt.
* **Account Statement:** Shows the transaction history for balance and credit.
* **Request Cards:** Issues new debit or credit cards.
### Teller Computer
* **Purpose:** For players with the `bank_teller` privilege to manage customer accounts.
* **Functions:**
* All functions of a normal ATM, but for any customer entered by name.
* Additional admin functions: Wipe Account and Reset PIN.
* Admin Search (Shift + Right-click): Server admins can check if an account is seized.
### Wire Transfer Machine (WTM)
* **Purpose:** Secure money transfers between players.
* **Operation:** Requires Bank Card and PIN.
* **Functions:**
* Transfer from your balance.
* Transfer from your credit line.
* Input fields for **Recipient**, **Amount** (decimals allowed), and **Purpose**.
* The recipient receives a chat message about the incoming funds.
* The transaction is logged in the account statements of both the sender and the recipient with all details.
### Card Swipe
* **Purpose:** Player-owned shops to sell items via card payment.
* **Functions:**
* **Seller:** Places the device, puts items in its inventory, and sets a price (decimals allowed).
* **Buyer:** Right-clicks the device with a debit or credit card to complete the purchase. The process is theft-proof items are only transferred automatically after successful payment.
## Automatic Interest System (`interest.lua`)
* A performant background process calculates interest once every 24 hours (real-time).
* The interest rates for balance (default 0.5%) and credit debt (default 3%) can be easily configured at the beginning of the `interest.lua` file.
* Interest transactions are automatically logged in the account statement.
## Chat Commands
All commands start with `bank_`.
#### For All Players
* `/bank_account [<playername>]` - Shows your own account balance. Players with `bank_teller` or `server` privs can also view others' accounts.
* `/bank_set_pin <4-digit-PIN>` - Sets your own PIN. (Replaced in function by the PIN Terminal but remains as a command).
#### For Admins Only (`server` privilege)
* `/bank_add <name> <number>` - Adds money to a player's account (integers only).
* `/bank_subtract <name> <number>` - Subtracts money from a player's account (integers only).
* `/bank_balance <name> <number>` - Sets a player's balance to an exact value (decimals allowed).
* `/bank_credit <name> <number>` - Sets a player's credit debt to an exact value (decimals allowed). `/bank_credit <name> 0` forgives all debt.
* `/bank_wipe <name>` - Wipes a player's account balance to 0.
* `/bank_seize <name>` - Seizes a player's account (locks access).
* `/bank_unseize <name>` - Unseizes an account.
## Improvements Over the Original Version
* **Stability:** The fundamental logic was overhauled to fix crashes and "freezing" or silently closing windows.
* **Security / Exploits:** Numerous exploits have been fixed, e.g., money duplication by moving items between slots, and withdrawing fractional amounts of cash.
* **Multiplayer Capability:** Almost all global variables that caused conflicts between players have been removed. The mod is now significantly more stable on servers. *(Note: One global variable for position was retained for compatibility with certain Minetest clients, which could in theory cause issues with perfectly simultaneous interactions at two devices, but this is extremely unlikely in practice).*
* **API:** A clean and safe API in `functions.lua` now allows other mods to interact with the system easily and without conflicts.
* **New Features:** Account Statements, Daily Interest System, Wire Transfer Machine, and the PIN Terminal have been newly added.
* **User Experience:** The onboarding for new players via the PIN Terminal is much more immersive. The UI has been polished in many places.
* **Internationalization (i18n):** The entire mod is now fully translatable.
---
## License
```
License: MIT
Copyright 2016 Trent Lasich
Copyright 2025 Rage87
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.
```
## Credits
* Original Mod: Tmanyo (Trent Lasich)
* Complete Redo, Extension & Bugfixing: **Rage87**
Bank Teller's Computer:
- This is used by people who have the bank_teller privilege. It has all the features an ATM does and allows for wiping a player's account, forgiving credit debt, reseting PINs, and over-viewing player's account statistics. If you are an admin, you can press E and rightclick this node, to search player names to check if they are seized or not.

333
atm.lua
View file

@ -1,322 +1,71 @@
--[[
ATM Node
--------
This file defines the Automatic Teller Machine (ATM) node and all
of its associated forms and logic for player interaction.
--]]
-- atm.lua (Angepasst mit Fehler-Nachricht für besetztes System)
-- A global variable to store the position of the last used machine.
-- This is necessary for stability in the user's specific environment.
-- ... (Alle Funktionen bis on_player_receive_fields bleiben identisch) ...
pos_info = {}
-- The interest rate for the monthly credit payment.
local credit_rate = 0.04
minetest.register_on_joinplayer(function(player) local name = player:get_player_name(); if not bank_accounts.player_has_account(name) then bank_accounts.create_account(name) end end)
minetest.register_craftitem("bank_accounts:atm_card", {description = S("ATM Card"), inventory_image = "atm_card.png", groups = {not_in_creative_inventory=1}, stack_max = 1})
local function show_atm_statement_form(player, account_type) local player_name = player:get_player_name(); local data = bank_accounts.get_account_data(player_name); local history = data.history; local lines = {}; local current_total_label = ""; table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner")))); table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------")); if history then for _, t in ipairs(history) do if t.account == account_type then local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type))); local purpose = t.purpose or ""; local partner = t.other or ""; if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end; table.insert(lines, minetest.formspec_escape(table.concat(parts, " | "))) end end end; if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance) .. " MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit) .. " MG") end; local form_name = "bank_accounts:atm_statement@" .. player_name; local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1", player_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"; minetest.show_formspec(player_name, form_name, formspec) end
function main_form(player, pos) local player_name = player:get_player_name(); local data = bank_accounts.get_account_data(player_name); local next_rate = 0; if data.credit > 0 then next_rate = math.max(1, math.floor(data.credit * credit_rate)) end; minetest.show_formspec(player_name, "bank_accounts:atm_options", "size[8,8]" .. "button_exit[1,.5;2,1;withdrawal;"..S("Withdraw").."]" .. "button_exit[1,1.5;2,1;deposit;"..S("Deposit").."]" .. "button_exit[1,2.5;3,1;pay_credit;"..S("Pay Credit Debt").."]" .. "label[4.5,0.8;"..S("Account Balance: @1", string.format("%.2f", data.balance) .. " MG").."]" .. "label[4.5,1.3;"..S("Total Credit Debt: @1", string.format("%.2f", data.credit) .. " MG").."]" .. "label[4.5,1.8;"..S("Next Rate: @1", tostring(next_rate) .. " MG").."]" .. "button_exit[4.5,2.5;3,1;statement;"..S("Account Statement").."]" .. "button_exit[1,3.5;3,1;credit_card;"..S("Get Credit Card").."]" .. "button_exit[1,4.5;3,1;debit_card;"..S("Get Debit Card").."]" .. "button_exit[5,7;2,1;exit;"..S("Close").."]") end
minetest.register_node("bank_accounts:atm", { description = S("Automatic Teller Machine"), drawtype = "mesh", mesh = "atm.obj", paramtype = "light", paramtype2 = "facedir", tiles = {"atm_col.png"}, groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2}, on_construct = function(pos) local meta = minetest.get_meta(pos); local inv = meta:get_inventory(); inv:set_size("ones", 1); inv:set_size("fives", 1); inv:set_size("tens", 1); inv:set_size("withdrawal", 3) end, on_rightclick = function(pos, node, player, itemstack, pointed_thing) pos_info = pos; if itemstack:get_name() ~= "bank_accounts:atm_card" then minetest.chat_send_player(player:get_player_name(), S("[ATM] Must use ATM card.")); return end; minetest.show_formspec(player:get_player_name(), "bank_accounts:atm_home", "size[8,8]" .. "pwdfield[2,4;4,1;fourdigitpin;"..S("Four Digit Pin:").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]") end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) if listname == "ones" and stack:get_name() ~= "currency:minegeld" then return 0 end; if listname == "fives" and stack:get_name() ~= "currency:minegeld_5" then return 0 end; if listname == "tens" and stack:get_name() ~= "currency:minegeld_10" then return 0 end; return stack:get_count() end, allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) local inv = minetest.get_meta(pos):get_inventory(); local stack = inv:get_stack(from_list, from_index); if to_list == "ones" and stack:get_name() ~= "currency:minegeld" then return 0 end; if to_list == "fives" and stack:get_name() ~= "currency:minegeld_5" then return 0 end; if to_list == "tens" and stack:get_name() ~= "currency:minegeld_10" then return 0 end; return count end })
-- Shows the account statement form for the player using the machine.
-- @param player: The player object.
-- @param account_type: "balance" or "credit" to determine which history to show.
local function show_atm_statement_form(player, account_type)
local player_name = player:get_player_name()
local data = bank_accounts.get_account_data(player_name)
local history = data.history
local lines = {}
local current_total_label = ""
-- Header for the textlist
table.insert(lines, minetest.formspec_escape(
string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s",
S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))
))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
-- Populate the list with transaction data
if history then
for _, t in ipairs(history) do
if t.account == account_type then
local parts = {}
table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp)))
table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG"))
table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG"))
table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = t.purpose or ""
local partner = t.other or ""
if purpose ~= "" or partner ~= "" then
table.insert(parts, string.format("%-20s", purpose))
if partner ~= "" then
table.insert(parts, partner)
end
end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end
end
end
-- Set the label for the current total based on the selected account type
if account_type == "balance" then
current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance) .. " MG")
else
current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit) .. " MG")
end
local form_name = "bank_accounts:atm_statement@" .. player_name
local formspec = "size[13,9]" ..
"label[0,0;"..S("Account Statement for @1", player_name).."]" ..
"button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]" ..
"button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]" ..
"textlist[0,1.2;13,7;statement_list;"..table.concat(lines, ",").."]"..
"label[0,8.4;"..current_total_label.."]"..
"button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player_name, form_name, formspec)
end
-- Shows the main menu form for the ATM.
function main_form(player, pos)
local player_name = player:get_player_name()
local data = bank_accounts.get_account_data(player_name)
local next_rate = 0
if data.credit > 0 then
next_rate = math.max(1, math.floor(data.credit * credit_rate))
end
minetest.show_formspec(player_name, "bank_accounts:atm_options",
"size[8,8]" ..
"button_exit[1,.5;2,1;withdrawal;"..S("Withdraw").."]" ..
"button_exit[1,1.5;2,1;deposit;"..S("Deposit").."]" ..
"button_exit[1,2.5;3,1;pay_credit;"..S("Pay Credit Debt").."]" ..
"label[4.5,0.8;"..S("Account Balance: @1", string.format("%.2f", data.balance) .. " MG").."]" ..
"label[4.5,1.3;"..S("Total Credit Debt: @1", string.format("%.2f", data.credit) .. " MG").."]" ..
"label[4.5,1.8;"..S("Next Rate: @1", tostring(next_rate) .. " MG").."]" ..
"button_exit[4.5,2.5;3,1;statement;"..S("Account Statement").."]" ..
"button_exit[1,3.5;3,1;credit_card;"..S("Get Credit Card").."]" ..
"button_exit[1,4.5;3,1;debit_card;"..S("Get Debit Card").."]" ..
"button_exit[5,7;2,1;exit;"..S("Close").."]")
end
-- Node definition for the ATM
minetest.register_node("bank_accounts:atm", {
description = S("Automatic Teller Machine"),
drawtype = "mesh",
mesh = "atm.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"atm_col.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
-- Create inventories when the node is placed in the world.
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size("ones", 1)
inv:set_size("fives", 1)
inv:set_size("tens", 1)
inv:set_size("withdrawal", 3)
end,
-- Called when a player right-clicks the node.
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos -- Set the global position for other functions to use.
if itemstack:get_name() ~= "bank_accounts:atm_card" then
minetest.chat_send_player(player:get_player_name(), S("[ATM] Must use ATM card."))
return
end
-- Show the initial PIN entry form.
minetest.show_formspec(player:get_player_name(), "bank_accounts:atm_home",
"size[8,8]" ..
"pwdfield[2,4;4,1;fourdigitpin;"..S("Four Digit Pin:").."]" ..
"button_exit[5,6;2,1;enter;"..S("Enter").."]" ..
"button_exit[3,6;2,1;exit;"..S("Cancel").."]")
end,
-- Prevents players from putting incorrect items into the deposit slots.
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname == "ones" and stack:get_name() ~= "currency:minegeld" then return 0 end
if listname == "fives" and stack:get_name() ~= "currency:minegeld_5" then return 0 end
if listname == "tens" and stack:get_name() ~= "currency:minegeld_10" then return 0 end
return stack:get_count()
end,
-- Prevents players from moving items between the typed deposit slots.
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
local inv = minetest.get_meta(pos):get_inventory()
local stack = inv:get_stack(from_list, from_index)
if to_list == "ones" and stack:get_name() ~= "currency:minegeld" then return 0 end
if to_list == "fives" and stack:get_name() ~= "currency:minegeld_5" then return 0 end
if to_list == "tens" and stack:get_name() ~= "currency:minegeld_10" then return 0 end
return count
end
})
-- Central handler for all formspec interactions related to the ATM.
minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Only process forms belonging to this node.
if not formname:find("bank_accounts:atm") then
return
if not formname:find("bank_accounts:atm") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
main_form(player, pos)
end
local player_name = player:get_player_name()
local pos = pos_info
if not pos then
return
end
-- Handle PIN entry form.
if formname == "bank_accounts:atm_home" then
if fields.enter then
if bank_accounts.get_pin(player_name) == fields.fourdigitpin then
main_form(player, pos)
else
minetest.chat_send_player(player_name, S("[ATM] Invalid Pin."))
end
end
-- Handle main menu options.
if fields.enter then if bank_accounts.get_pin(player_name) == fields.fourdigitpin then main_form(player, pos) else minetest.chat_send_player(player_name, S("[ATM] Invalid Pin.")) end end
elseif formname == "bank_accounts:atm_options" then
if fields.withdrawal then
minetest.get_meta(pos):get_inventory():set_size("withdrawal", 3)
minetest.show_formspec(player_name, "bank_accounts:atm_withdrawal",
"size[8,8]" ..
"field[2,4;5,1;money;"..S("Amount:")..";]" ..
"button_exit[3,6;2,1;exit;"..S("Cancel").."]" ..
"button_exit[5,6;2,1;enter;"..S("Enter").."]")
elseif fields.deposit then
local inv = minetest.get_meta(pos):get_inventory()
inv:set_size("ones", 1); inv:set_size("fives", 1); inv:set_size("tens", 1)
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
minetest.show_formspec(player_name, "bank_accounts:atm_deposit",
"size[8,8]" ..
"label[1,.5;1 MG]" .. "label[2,.5;5 MG]" .. "label[3,.5;10 MG]" ..
"list["..list_name..";ones;.75,1;1,1]" ..
"list["..list_name..";fives;1.75,1;1,1]" ..
"list["..list_name..";tens;2.75,1;1,1]" ..
"list[current_player;main;0,3;8,4;]" ..
"button_exit[3,7;2,1;exit;"..S("Cancel").."]" ..
"button_exit[5,7;2,1;enter;"..S("Enter").."]")
if fields.withdrawal then minetest.get_meta(pos):get_inventory():set_size("withdrawal", 3); minetest.show_formspec(player_name, "bank_accounts:atm_withdrawal", "size[8,8]" .. "field[2,4;5,1;money;"..S("Amount:")..";]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]")
elseif fields.deposit then local inv = minetest.get_meta(pos):get_inventory(); inv:set_size("ones", 1); inv:set_size("fives", 1); inv:set_size("tens", 1); local list_name = "nodemeta:"..pos.x..","..pos.y..","..pos.z; minetest.show_formspec(player_name, "bank_accounts:atm_deposit", "size[8,8]" .. "label[1,.5;1 MG]" .. "label[2,.5;5 MG]" .. "label[3,.5;10 MG]" .. "list["..list_name..";ones;.75,1;1,1]" .. "list["..list_name..";fives;1.75,1;1,1]" .. "list["..list_name..";tens;2.75,1;1,1]" .. "list[current_player;main;0,3;8,4;]" .. "button_exit[3,7;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,7;2,1;enter;"..S("Enter").."]")
elseif fields.pay_credit then
local credit_debt = bank_accounts.get_credit(player_name)
local next_rate = 0
if credit_debt > 0 then
next_rate = math.max(1, math.floor(credit_debt * credit_rate))
end
minetest.show_formspec(player_name, "bank_accounts:atm_pay_credit",
"size[8,8]" ..
"label[0,0.5;"..S("Total Credit Debt: @1", string.format("%.2f", credit_debt).." MG").."]"..
"label[0,2;"..S("Next calculated rate:").."]"..
"button_exit[0,2.5;3.5,1;pay_rate;"..S("Pay Next Rate (@1 MG)", next_rate).."]" ..
"label[4.2,2;"..S("Or enter a custom amount:").."]" ..
"field[4.5,2.8;3.5,1;custom_amount;"..""..";]" ..
"button_exit[4.2,3.5;3.5,1;pay_custom;"..S("Pay Custom Amount").."]" ..
"button_exit[3,7;2,1;exit;"..S("Cancel").."]")
elseif fields.statement then
show_atm_statement_form(player, "balance")
elseif fields.credit_card then
player:get_inventory():add_item("main", "bank_accounts:credit_card")
main_form(player, pos)
elseif fields.debit_card then
player:get_inventory():add_item("main", "bank_accounts:debit_card")
main_form(player, pos)
end
-- Handle withdrawal form.
local credit_debt = bank_accounts.get_credit(player_name); local next_rate = 0; if credit_debt > 0 then next_rate = math.max(1, math.floor(credit_debt * credit_rate)) end
minetest.show_formspec(player_name, "bank_accounts:atm_pay_credit", "size[8,8]" .. "label[0,0.5;"..S("Total Credit Debt: @1", string.format("%.2f", credit_debt).." MG").."]".. "label[0,2;"..S("Next calculated rate:").."]".. "button_exit[0,2.5;3.5,1;pay_rate;"..S("Pay Next Rate (@1 MG)", next_rate).."]" .. "label[4.2,2;"..S("Or enter a custom amount:").."]" .. "field[4.5,2.8;3.5,1;custom_amount;"..""..";]" .. "button_exit[4.2,3.5;3.5,1;pay_custom;"..S("Pay Custom Amount").."]" .. "button_exit[3,7;2,1;exit;"..S("Cancel").."]")
elseif fields.statement then show_atm_statement_form(player, "balance")
elseif fields.credit_card then player:get_inventory():add_item("main", "bank_accounts:credit_card"); main_form(player, pos)
elseif fields.debit_card then player:get_inventory():add_item("main", "bank_accounts:debit_card"); main_form(player, pos) end
elseif formname == "bank_accounts:atm_withdrawal" then
if fields.enter then
local amount = tonumber(fields.money)
if amount and amount > 0 and amount % 1 == 0 and bank_accounts.get_balance(player_name) >= amount then
bank_accounts.add_balance(player_name, -amount, "ATM Withdrawal", "", "")
local tens=math.floor(amount/10); local rem=amount%10; local fives=math.floor(rem/5); local ones=rem%5
local inv = minetest.get_meta(pos):get_inventory()
inv:set_stack("withdrawal", 1, {name="currency:minegeld_10", count=tens})
inv:set_stack("withdrawal", 2, {name="currency:minegeld_5", count=fives})
inv:set_stack("withdrawal", 3, {name="currency:minegeld", count=ones})
local list_name = "nodemeta:"..pos.x..","..pos.y..","..pos.z
minetest.show_formspec(player_name, "bank_accounts:withdrawn_money",
"size[8,8]" ..
"label[1,0.5;"..S("Please take your money.").. "]" ..
"list["..list_name..";withdrawal;1,1;3,1]" ..
"list[current_player;main;0,3;8,4;]" ..
"button_exit[3,7;2,1;exit;"..S("Close").."]")
else
minetest.chat_send_player(player_name, S("[ATM] Insufficient funds or invalid amount."))
main_form(player, pos)
end
else
main_form(player, pos)
end
-- Handle deposit form.
if not bank_accounts.add_balance(player_name, -amount, "ATM Withdrawal", "", "") then on_fail() return end
local tens=math.floor(amount/10); local rem=amount%10; local fives=math.floor(rem/5); local ones=rem%5; local inv = minetest.get_meta(pos):get_inventory(); inv:set_stack("withdrawal", 1, {name="currency:minegeld_10", count=tens}); inv:set_stack("withdrawal", 2, {name="currency:minegeld_5", count=fives}); inv:set_stack("withdrawal", 3, {name="currency:minegeld", count=ones}); local list_name = "nodemeta:"..pos.x..","..pos.y..","..pos.z; minetest.show_formspec(player_name, "bank_accounts:withdrawn_money", "size[8,8]" .. "label[1,0.5;"..S("Please take your money.").. "]" .. "list["..list_name..";withdrawal;1,1;3,1]" .. "list[current_player;main;0,3;8,4;]" .. "button_exit[3,7;2,1;exit;"..S("Close").."]")
else minetest.chat_send_player(player_name, S("[ATM] Insufficient funds or invalid amount.")); main_form(player, pos) end
else main_form(player, pos) end
elseif formname == "bank_accounts:atm_deposit" then
local inv = minetest.get_meta(pos):get_inventory()
if fields.enter then
local total_deposit = inv:get_stack("ones",1):get_count() + inv:get_stack("fives",1):get_count()*5 + inv:get_stack("tens",1):get_count()*10
if total_deposit > 0 then
bank_accounts.add_balance(player_name, total_deposit, "ATM Deposit", "", "")
minetest.chat_send_player(player_name, S("[ATM] Deposited @1.", tostring(total_deposit).." MG"))
end
else
-- Return items to player if they cancel.
local player_inv = player:get_inventory()
player_inv:add_item("main", inv:get_stack("ones",1))
player_inv:add_item("main", inv:get_stack("fives",1))
player_inv:add_item("main", inv:get_stack("tens",1))
end
inv:set_stack("ones",1,nil); inv:set_stack("fives",1,nil); inv:set_stack("tens",1,nil)
main_form(player, pos)
-- Handle form after taking withdrawn money.
elseif formname == "bank_accounts:withdrawn_money" then
main_form(player, pos)
-- Handle credit payment form.
if total_deposit > 0 then if not bank_accounts.add_balance(player_name, total_deposit, "ATM Deposit", "", "") then on_fail() return end; minetest.chat_send_player(player_name, S("[ATM] Deposited @1.", tostring(total_deposit).." MG")) end
else local player_inv = player:get_inventory(); player_inv:add_item("main", inv:get_stack("ones",1)); player_inv:add_item("main", inv:get_stack("fives",1)); player_inv:add_item("main", inv:get_stack("tens",1)) end
inv:set_stack("ones",1,nil); inv:set_stack("fives",1,nil); inv:set_stack("tens",1,nil); main_form(player, pos)
elseif formname == "bank_accounts:withdrawn_money" then main_form(player, pos)
elseif formname == "bank_accounts:atm_pay_credit" then
local amount_to_pay = 0
local is_valid = false
if fields.pay_rate then
local credit_debt = bank_accounts.get_credit(player_name)
if credit_debt > 0 then
amount_to_pay = math.max(1, math.floor(credit_debt * credit_rate))
end
is_valid = true
local amount_to_pay = 0; local is_valid = false
if fields.pay_rate then amount_to_pay = math.max(1, math.floor(bank_accounts.get_credit(player_name) * credit_rate)); is_valid = true
elseif fields.pay_custom then
amount_to_pay = normalize_and_tonumber(fields.custom_amount)
local min_payment = 0
local credit_debt = bank_accounts.get_credit(player_name)
if credit_debt > 0 then
min_payment = math.max(1, math.floor(credit_debt * credit_rate))
amount_to_pay = normalize_and_tonumber(fields.custom_amount); local min_payment = math.max(1, math.floor(bank_accounts.get_credit(player_name) * credit_rate))
if not amount_to_pay or amount_to_pay < min_payment then minetest.chat_send_player(player_name, S("[ATM] You must pay at least the minimum monthly rate."));
else is_valid = true end
end
if not amount_to_pay or amount_to_pay < min_payment then
minetest.chat_send_player(player_name, S("[ATM] You must pay at least the minimum monthly rate."))
else
is_valid = true
end
end
if is_valid and amount_to_pay and amount_to_pay > 0 then
if bank_accounts.get_balance(player_name) < amount_to_pay then
minetest.chat_send_player(player_name, S("[ATM] Insufficient funds."))
elseif bank_accounts.get_credit(player_name) < amount_to_pay then
minetest.chat_send_player(player_name, S("[ATM] You don't have that much credit debt."))
if bank_accounts.get_balance(player_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[ATM] Insufficient funds."))
elseif bank_accounts.get_credit(player_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[ATM] You don't have that much credit debt."))
else
bank_accounts.add_balance(player_name, -amount_to_pay, "Rate Payment", "", "")
bank_accounts.add_credit(player_name, -amount_to_pay, "Rate Payment", "", "")
if not bank_accounts.add_balance(player_name, -amount_to_pay, "Rate Payment", "", "") then on_fail() return end
if not bank_accounts.add_credit(player_name, -amount_to_pay, "Rate Payment", "", "") then on_fail() return end
minetest.chat_send_player(player_name, S("[ATM] Paid @1 of credit debt.", string.format("%.2f", amount_to_pay).." MG"))
end
elseif fields.pay_custom and is_valid == false then
-- Do nothing, error message was already sent.
elseif not fields.exit and not fields.quit and (fields.pay_rate or fields.pay_custom) then
-- Catch other invalid inputs.
minetest.chat_send_player(player_name, S("[ATM] Invalid amount."))
end
elseif not fields.exit and not fields.quit and (fields.pay_rate or fields.pay_custom) then minetest.chat_send_player(player_name, S("[ATM] Invalid amount.")) end
main_form(player, pos)
-- Handle statement form.
elseif formname:find("bank_accounts:atm_statement@") then
local target_name = formname:match("bank_accounts:atm_statement@(.*)")
if not target_name then return end
if fields.back then
main_form(player, pos)
else
show_atm_statement_form(player, fields.view_credit and "credit" or "balance")
end
local target_name = formname:match("bank_accounts:atm_statement@(.*)"); if not target_name then return end
if fields.back then main_form(player, pos) else show_atm_statement_form(player, fields.view_credit and "credit" or "balance") end
end
end)

325
atm2.lua Normal file
View file

@ -0,0 +1,325 @@
-- atm2.lua (Doppelte Übersetzung im Kontoauszug behoben)
-- A global variable to store the position of the last used machine.
pos_info = {}
-- This table defines all valid currency items and their values.
local currency_values = {
{name = "currency:minegeld_100", value = 100},
{name = "currency:minegeld_50", value = 50},
{name = "currency:minegeld_10", value = 10},
{name = "currency:minegeld_5", value = 5},
{name = "currency:minegeld", value = 1},
{name = "currency:minegeld_cent_25", value = 0.25},
{name = "currency:minegeld_cent_10", value = 0.10},
{name = "currency:minegeld_cent_5", value = 0.05},
}
-- Helper function to round a number to a specific number of decimal places.
local function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
-- Helper function to get the value of a single currency itemstack.
local function get_item_value(itemstack)
local name = itemstack:get_name()
for _, currency in ipairs(currency_values) do
if currency.name == name then
return currency.value
end
end
return 0
end
-- Helper function to convert a numeric amount back into a list of item stacks.
local function amount_to_itemstacks(amount)
local items = {}
local remaining_amount = amount
for _, currency in ipairs(currency_values) do
if remaining_amount >= currency.value then
local count = math.floor(remaining_amount / currency.value)
if count > 0 then
table.insert(items, {name = currency.name, count = count})
remaining_amount = remaining_amount - (count * currency.value)
remaining_amount = round(remaining_amount, 2)
if remaining_amount < 0.001 then break end
end
end
end
return items
end
-- Shows the account statement form.
local function show_atm2_statement_form(player, account_type)
local player_name = player:get_player_name()
local data = bank_accounts.get_account_data(player_name)
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
-- KORREKTUR: 'purpose' wird direkt angezeigt, nicht erneut übersetzt.
local purpose = t.purpose or ""
local partner = t.other or ""
if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance) .. " MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit) .. " MG") end
local form_name = "bank_accounts:atm2_statement@" .. player_name
local formspec = "size[13,9]" .. "label[0,0;"..S("Account Statement for @1", player_name).."]" .. "button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]" .. "button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]" .. "textlist[0,1.2;13,7;statement_list;"..table.concat(lines, ",").."]".. "label[0,8.4;"..current_total_label.."]".. "button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player_name, form_name, formspec)
end
-- Shows the main menu for the ATM Mark II.
local function atm2_main_form(player, pos)
local player_name = player:get_player_name()
local data = bank_accounts.get_account_data(player_name)
local next_rate = 0
if data.credit > 0 then
next_rate = math.max(1, math.floor(data.credit * 0.04))
end
minetest.show_formspec(player_name, "bank_accounts:atm2_options",
"size[8,8]" ..
"button_exit[1,.5;2,1;withdrawal;"..S("Withdraw").."]" ..
"button_exit[1,1.5;2,1;deposit;"..S("Deposit").."]" ..
"button_exit[1,2.5;3,1;pay_credit;"..S("Pay Credit Debt").."]" ..
"label[4.5,0.8;"..S("Account Balance: @1", string.format("%.2f", data.balance) .. " MG").."]" ..
"label[4.5,1.3;"..S("Total Credit Debt: @1", string.format("%.2f", data.credit) .. " MG").."]" ..
"label[4.5,1.8;"..S("Next Rate: @1", tostring(next_rate) .. " MG").."]" ..
"button_exit[4.5,2.5;3,1;statement;"..S("Account Statement").."]" ..
"button_exit[1,3.5;3,1;credit_card;"..S("Get Credit Card").."]" ..
"button_exit[1,4.5;3,1;debit_card;"..S("Get Debit Card").."]" ..
"button_exit[5,7;2,1;exit;"..S("Close").."]")
end
-- Shows the new deposit form with a single "swallow" slot.
local function show_atm2_deposit_form(player, pos)
local player_name = player:get_player_name()
local meta = minetest.get_meta(pos)
local deposited_amount = tonumber(meta:get_string("deposit_buffer") or "0")
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
minetest.show_formspec(player_name, "bank_accounts:atm2_deposit",
"size[8,9]"..
"label[0,0.5;"..S("Place coins and notes in the slot below.").."]"..
"list["..list_name..";deposit_slot;3.5,1.5;1,1;]"..
"label[0,3;"..S("Currently Deposited: @1 MG", string.format("%.2f", deposited_amount)).."]"..
"button_exit[0.5,4;3,1;confirm_deposit;"..S("Deposit").."]"..
"button_exit[4.5,4;3,1;return_deposit;"..S("Return").."]"..
"list[current_player;main;0,5;8,4;]")
end
minetest.register_node("bank_accounts:atm2", {
description = S("ATM Mark II"),
drawtype = "mesh",
mesh = "atm.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"atm2_col.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size("deposit_slot", 1)
for _, currency in ipairs(currency_values) do
inv:set_size(currency.name, 1)
end
end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
if itemstack:get_name() ~= "bank_accounts:atm_card" then
minetest.chat_send_player(player:get_player_name(), S("[ATM] Must use ATM card."))
return
end
minetest.show_formspec(player:get_player_name(), "bank_accounts:atm2_home",
"size[8,8]" .. "pwdfield[2,4;4,1;fourdigitpin;"..S("Four Digit Pin:").."]" ..
"button_exit[5,6;2,1;enter;"..S("Enter").."]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]")
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname ~= "deposit_slot" then return end
local value = get_item_value(stack)
if value > 0 then
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local current_buffer = tonumber(meta:get_string("deposit_buffer") or "0")
meta:set_string("deposit_buffer", tostring(current_buffer + value * stack:get_count()))
inv:set_stack(listname, index, "")
minetest.after(0, show_atm2_deposit_form, player, pos)
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:atm2") then return end
local player_name = player:get_player_name()
local pos = pos_info
if not pos then return end
local meta = minetest.get_meta(pos)
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
atm2_main_form(player, pos)
end
if formname == "bank_accounts:atm2_home" then
if fields.enter then if bank_accounts.get_pin(player_name) == fields.fourdigitpin then atm2_main_form(player, pos) else minetest.chat_send_player(player_name, S("[ATM] Invalid Pin.")) end end
elseif formname == "bank_accounts:atm2_options" then
if fields.deposit then
meta:set_string("deposit_buffer", "0")
show_atm2_deposit_form(player, pos)
elseif fields.withdrawal then
minetest.show_formspec(player_name, "bank_accounts:atm2_withdrawal", "size[8,8]" .. "field[2,4;5,1;money;"..S("Amount:")..";]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]")
elseif fields.pay_credit then
local credit_debt = bank_accounts.get_credit(player_name)
local next_rate = 0
if credit_debt > 0 then
next_rate = math.max(1, math.floor(credit_debt * 0.04))
end
minetest.show_formspec(player_name, "bank_accounts:atm2_pay_credit", "size[8,8]" ..
"label[0,0.5;"..S("Total Credit Debt: @1", string.format("%.2f", credit_debt).." MG").."]"..
"label[0,2;"..S("Next calculated rate:").."]"..
"button_exit[0,2.5;3.5,1;pay_rate;"..S("Pay Next Rate (@1 MG)", next_rate).."]" ..
"label[4.2,2;"..S("Or enter a custom amount:").."]" ..
"field[4.5,2.8;3.5,1;custom_amount;"..""..";]" ..
"button_exit[4.2,3.5;3.5,1;pay_custom;"..S("Pay Custom Amount").."]" ..
"button_exit[3,7;2,1;exit;"..S("Cancel").."]")
elseif fields.statement then
show_atm2_statement_form(player, "balance")
elseif fields.credit_card then
player:get_inventory():add_item("main", "bank_accounts:credit_card")
atm2_main_form(player, pos)
elseif fields.debit_card then
player:get_inventory():add_item("main", "bank_accounts:debit_card")
atm2_main_form(player, pos)
end
elseif formname == "bank_accounts:atm2_deposit" then
local buffer = tonumber(meta:get_string("deposit_buffer") or "0")
if fields.confirm_deposit then
if buffer > 0 then
if not bank_accounts.add_balance(player_name, buffer, "ATM Deposit", "", "") then on_fail(); return end
minetest.chat_send_player(player_name, S("[ATM] Deposited @1.", string.format("%.2f", buffer).." MG"))
end
elseif fields.return_deposit or fields.quit then
if buffer > 0 then
local items_to_return = amount_to_itemstacks(buffer)
for _, item in ipairs(items_to_return) do
player:get_inventory():add_item("main", item)
end
minetest.chat_send_player(player_name, S("[ATM] Your deposit was returned to your inventory."))
end
end
meta:set_string("deposit_buffer", "0")
if not fields.quit then
atm2_main_form(player, pos)
end
elseif formname == "bank_accounts:atm2_withdrawal" then
if fields.enter then
local requested_amount = normalize_and_tonumber(fields.money)
if requested_amount and requested_amount > 0 and bank_accounts.get_balance(player_name) >= requested_amount then
local items_to_dispense = amount_to_itemstacks(requested_amount)
local dispensable_amount = 0
for _, item_data in ipairs(items_to_dispense) do
for _, currency in ipairs(currency_values) do
if currency.name == item_data.name then
dispensable_amount = dispensable_amount + (currency.value * item_data.count)
break
end
end
end
if not bank_accounts.add_balance(player_name, -dispensable_amount, "ATM Withdrawal", "", "") then on_fail(); return end
local inv = meta:get_inventory()
local list_elements = ""
local warning_label = ""
if dispensable_amount < requested_amount then
warning_label = "label[0,1;`"..S("Note: Amount was rounded down to the nearest dispensable value.").."`]" .. "style[label;color=yellow]"
end
for i, item in ipairs(items_to_dispense) do
inv:set_stack(item.name, 1, {name = item.name, count = item.count})
local x_pos = i - 1
list_elements = list_elements .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";"..item.name..";"..x_pos..",2;1,1;]"
end
minetest.show_formspec(player_name, "bank_accounts:atm2_withdrawal_output",
"size[8,4]" ..
"label[0,0.5;"..S("Please take your money (@1 MG):", string.format("%.2f", dispensable_amount)).."]" ..
warning_label .. list_elements ..
"button_exit[2.5,3.25;3,1;take_all;"..S("Take All").."]")
else
minetest.chat_send_player(player_name, S("[ATM] Insufficient funds or invalid amount."))
atm2_main_form(player, pos)
end
else
atm2_main_form(player, pos)
end
elseif formname == "bank_accounts:atm2_withdrawal_output" then
local inv = meta:get_inventory()
local player_inv = player:get_inventory()
if fields.take_all or fields.quit then
local any_left = false
for _, currency in ipairs(currency_values) do
local stack = inv:get_stack(currency.name, 1)
if not stack:is_empty() then
player_inv:add_item("main", stack)
inv:set_stack(currency.name, 1, "")
any_left = true
end
end
if any_left and fields.quit then
minetest.chat_send_player(player_name, S("[ATM] Withdrawn money has been moved to your inventory."))
end
end
if not fields.quit then
atm2_main_form(player, pos)
end
elseif formname == "bank_accounts:atm2_pay_credit" then
local amount_to_pay = 0; local is_valid = false
if fields.pay_rate then
local credit_debt = bank_accounts.get_credit(player_name)
if credit_debt > 0 then
amount_to_pay = math.max(1, math.floor(credit_debt * 0.04))
end
is_valid = true
elseif fields.pay_custom then
amount_to_pay = normalize_and_tonumber(fields.custom_amount)
local min_payment = 0
local credit_debt = bank_accounts.get_credit(player_name)
if credit_debt > 0 then
min_payment = math.max(1, math.floor(credit_debt * 0.04))
end
if not amount_to_pay or amount_to_pay < min_payment then
minetest.chat_send_player(player_name, S("[ATM] You must pay at least the minimum monthly rate."))
else
is_valid = true
end
end
if is_valid and amount_to_pay and amount_to_pay > 0 then
if bank_accounts.get_balance(player_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[ATM] Insufficient funds."))
elseif bank_accounts.get_credit(player_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[ATM] You don't have that much credit debt."))
else
if not bank_accounts.add_balance(player_name, -amount_to_pay, "Rate Payment", "", "") then on_fail(); return end
if not bank_accounts.add_credit(player_name, -amount_to_pay, "Rate Payment", "", "") then on_fail(); return end
minetest.chat_send_player(player_name, S("[ATM] Paid @1 of credit debt.", string.format("%.2f", amount_to_pay).." MG"))
end
elseif fields.pay_custom and not is_valid then
elseif not fields.exit and not fields.quit and (fields.pay_rate or fields.pay_custom) then minetest.chat_send_player(player_name, S("[ATM] Invalid amount.")) end
atm2_main_form(player, pos)
elseif formname:find("bank_accounts:atm2_statement@") then
local target_name = formname:match("bank_accounts:atm2_statement@(.*)"); if not target_name then return end
if fields.back then atm2_main_form(player, pos) else show_atm2_statement_form(player, fields.view_credit and "credit" or "balance") end
end
end)

152
bank_safe.lua Normal file
View file

@ -0,0 +1,152 @@
-- bank_safe.lua (Korrigierter Zugriff für besitzende Teller)
local safe_setup_fee = 50
local function get_safe_formspec(pos, owner_name, clicker)
local pos_str = pos.x .. "," .. pos.y .. "," .. pos.z
local formspec = "size[8,9]".. "label[0,0;"..S("Safe owned by: @1", owner_name).."]".. "list[nodemeta:".. pos_str .. ";main;1,1;6,2;]".. "list[current_player;main;0,4;8,4;]".. "listring[nodemeta:".. pos_str .. ";main]".. "listring[current_player;main]"
-- Fügt den Verwaltungs-Button nur hinzu, wenn der Besitzer auch die nötigen Rechte hat
if clicker and minetest.check_player_privs(clicker:get_player_name(), {bank_teller=true, server=true}, true) and clicker:get_player_name() == owner_name then
formspec = formspec .. "button_exit[5,0;3,1;manage_safe;"..S("Manage Safe").."]"
end
return formspec
end
local function has_safe_privilege(meta, player)
local name = ""; if player then if minetest.check_player_privs(player, {protection_bypass=true}) then return true end; name = player:get_player_name() end
if name ~= meta:get_string("owner") then return false end; return true
end
minetest.register_node("bank_accounts:safe_vacant", {
description = S("Bank Safe"),
inventory_image = "safe_front_vacant.png",
paramtype = "light",
paramtype2 = "facedir",
tiles = { "safe_side.png", "safe_side.png", "safe_side.png", "safe_side.png", "safe_side.png", "safe_front_vacant.png" },
is_ground_content = false,
groups = {cracky=1, level=2},
on_construct = function(pos)
local meta = minetest.get_meta(pos); meta:set_string("infotext", S("Bank Safe (Unassigned)")); meta:set_string("owner", "")
local inv = meta:get_inventory(); inv:set_size("main", 6*2)
end,
can_dig = function(pos, player) return minetest.get_meta(pos):get_inventory():is_empty("main") end,
on_rightclick = function(pos, node, clicker, itemstack)
pos_info = pos
local clicker_name = clicker:get_player_name()
if minetest.check_player_privs(clicker_name, {bank_teller=true, server=true}, true) then
minetest.show_formspec(clicker_name, "bank_accounts:safe_setup",
"size[8,6]"..
"label[1,0.5;"..S("Assign or Claim Safe (0 MG)").."]"..
"field[1.2,1.5;5.8,1;owner_name;"..S("Player Name:")..";]"..
"button_exit[1,2.5;6,1;set_owner;"..S("Assign to Player").."]"..
"button_exit[1,4;6,1;claim_for_self;"..S("Claim for myself").."]")
else
if itemstack:get_name() ~= "bank_accounts:atm_card" then
minetest.chat_send_player(clicker_name, S("[Safe] Please use your bank card to claim a safe."))
return
end
minetest.show_formspec(clicker_name, "bank_accounts:safe_claim",
"size[8,4]"..
"label[1,1;"..S("Claim this safe for a one-time fee of @1 MG?", safe_setup_fee).."]"..
"button_exit[1,3;2.5,1;claim;"..S("Claim").."]"..
"button_exit[4.5,3;2.5,1;cancel;"..S("Cancel").."]")
end
end,
})
minetest.register_node("bank_accounts:safe_occupied", {
description = S("Bank Safe"),
inventory_image = "safe_front_occupied.png",
paramtype = "light",
paramtype2 = "facedir",
tiles = { "safe_side.png", "safe_side.png", "safe_side.png", "safe_side.png", "safe_side.png", "safe_front_occupied.png" },
is_ground_content = false,
groups = {cracky=1, level=2},
can_dig = function(pos, player) return false end,
on_rightclick = function(pos, node, clicker, itemstack)
pos_info = pos; local meta = minetest.get_meta(pos); local owner = meta:get_string("owner"); local clicker_name = clicker:get_player_name()
-- KORRIGIERTE LOGIK-REIHENFOLGE
-- Fall 1: Der Klicker IST der Besitzer (egal ob Teller oder nicht).
if has_safe_privilege(meta, clicker) then
if itemstack:get_name() ~= "bank_accounts:atm_card" then minetest.chat_send_player(clicker_name, S("[Safe] Please use your bank card to access.")); return end
minetest.show_formspec(clicker_name, "bank_accounts:safe_pin_entry", "size[8,5]".."label[1,0.5;"..S("Please enter your PIN to access the safe.").."]".."pwdfield[2,2;4,1;pin;"..S("PIN:").."]".."button_exit[2.5,4;3,1;enter;"..S("Enter").."]")
-- Fall 2: Der Klicker ist NICHT der Besitzer, hat aber Teller- oder Server-Rechte.
elseif minetest.check_player_privs(clicker_name, {bank_teller=true, server=true}, true) then
minetest.show_formspec(clicker_name, "bank_accounts:safe_manage", "size[8,6]".."label[1,0.5;"..S("Manage Safe for @1", owner).."]".."field[1,2;6,1;owner_name;"..S("Change Owner to:")..";"..owner.."]".."button_exit[1,3;3,1;set_owner;"..S("Set Owner").."]".."button_exit[4,3;3,1;unrent;"..S("Unrent (Clear Owner)").."]")
-- Fall 3: Alle anderen.
else
minetest.chat_send_player(clicker_name, S("This safe is locked."))
end
end,
})
local common_callbacks = {
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) if not has_safe_privilege(minetest.get_meta(pos), player) then return 0 end; return count end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player) if not has_safe_privilege(minetest.get_meta(pos), player) then return 0 end; return stack:get_count() end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player) if not has_safe_privilege(minetest.get_meta(pos), player) then return 0 end; return stack:get_count() end,
}
for name, func in pairs(common_callbacks) do
minetest.registered_nodes["bank_accounts:safe_vacant"][name] = func
minetest.registered_nodes["bank_accounts:safe_occupied"][name] = func
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:safe_") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local meta = minetest.get_meta(pos); local node = minetest.get_node(pos)
if formname == "bank_accounts:safe_setup" then
local target_owner = ""
if fields.set_owner then target_owner = fields.owner_name
elseif fields.claim_for_self then target_owner = player_name end
if target_owner ~= "" then
if not bank_accounts.player_has_account(target_owner) then minetest.chat_send_player(player_name, S("This player does not have a bank account and cannot be assigned a safe.")); return end
meta:set_string("owner", target_owner); meta:set_string("infotext", S("Bank Safe (owned by @1)", target_owner))
minetest.swap_node(pos, {name="bank_accounts:safe_occupied", param2=node.param2})
if target_owner == player_name then minetest.chat_send_player(player_name, S("You have claimed this safe for yourself."))
else minetest.chat_send_player(player_name, S("Safe at @1 successfully assigned to @2.", minetest.pos_to_string(pos), target_owner)) end
elseif fields.set_owner then minetest.chat_send_player(player_name, S("You must enter a name.")) end
elseif formname == "bank_accounts:safe_manage" then
if fields.set_owner then
local target_owner = fields.owner_name
if not target_owner or target_owner == "" then minetest.chat_send_player(player_name, S("You must enter a name.")); return end
if not bank_accounts.player_has_account(target_owner) then minetest.chat_send_player(player_name, S("This player does not have a bank account and cannot be assigned a safe.")); return end
meta:set_string("owner", target_owner); meta:set_string("infotext", S("Bank Safe (owned by @1)", target_owner))
minetest.chat_send_player(player_name, S("Safe at @1 successfully assigned to @2.", minetest.pos_to_string(pos), target_owner))
elseif fields.unrent then
meta:set_string("owner", ""); meta:set_string("infotext", S("Bank Safe (Unassigned)"))
minetest.swap_node(pos, {name="bank_accounts:safe_vacant", param2=node.param2})
minetest.chat_send_player(player_name, S("Ownership of safe at @1 has been cleared.", minetest.pos_to_string(pos)))
end
elseif formname == "bank_accounts:safe_inventory" then
if fields.manage_safe then
local owner = meta:get_string("owner")
minetest.show_formspec(player_name, "bank_accounts:safe_manage", "size[8,6]".."label[1,0.5;"..S("Manage Safe for @1", owner).."]".."field[1,2;6,1;owner_name;"..S("Change Owner to:")..";"..owner.."]".."button_exit[1,3;3,1;set_owner;"..S("Set Owner").."]".."button_exit[4,3;3,1;unrent;"..S("Unrent (Clear Owner)").."]")
end
elseif formname == "bank_accounts:safe_pin_entry" then
if fields.enter then
if fields.pin == bank_accounts.get_pin(player_name) then
minetest.show_formspec(player_name, "bank_accounts:safe_inventory", get_safe_formspec(pos, meta:get_string("owner"), player))
else
minetest.chat_send_player(player_name, S("[Safe] Invalid PIN."))
end
end
elseif formname == "bank_accounts:safe_claim" then
if fields.claim then
if bank_accounts.get_balance(player_name) >= safe_setup_fee then
local success = bank_accounts.add_balance(player_name, -safe_setup_fee, "Setup Fee", "Bank Safe Setup", "Bank")
if success then
meta:set_string("owner", player_name); meta:set_string("infotext", S("Bank Safe (owned by @1)", player_name))
minetest.swap_node(pos, {name="bank_accounts:safe_occupied", param2=node.param2})
minetest.chat_send_player(player_name, S("[Safe] You have successfully claimed this safe."))
else
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
else
minetest.chat_send_player(player_name, S("[Safe] You do not have enough money to claim this safe."))
end
end
end
end)

View file

@ -1,13 +1,7 @@
--[[
Card Swipe Node
---------------
This file defines the Card Swipe node, which allows players
to set up shops and sell items via card payments.
--]]
-- card_swipe.lua (Besitzer kann jetzt Items wieder entnehmen)
-- Uses the global 'pos_info' variable for consistency.
pos_info = {}
-- Node definition for the Card Swipe.
minetest.register_node("bank_accounts:card_swipe", {
description = S("Card Swipe"),
drawtype = "mesh",
@ -19,12 +13,10 @@ minetest.register_node("bank_accounts:card_swipe", {
selection_box = { type = "fixed", fixed = {{-.3,-.5,-.3,.4,-.2,.3}} },
collision_box = { type = "fixed", fixed = {{-.3,-.5,-.3,.4,-.2,.3}} },
-- Creates the item inventory when the node is placed.
on_construct = function(pos)
minetest.get_meta(pos):get_inventory():set_size("items", 8)
end,
-- Sets the owner of the node after it has been placed.
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local owner = placer:get_player_name()
@ -32,7 +24,6 @@ minetest.register_node("bank_accounts:card_swipe", {
meta:set_string("owner", owner)
end,
-- Ensures only the owner can dig the node, and only if it's empty.
can_dig = function(pos, player)
local meta = minetest.get_meta(pos)
if player:get_player_name() == meta:get_string("owner") then
@ -41,15 +32,13 @@ minetest.register_node("bank_accounts:card_swipe", {
return false
end,
-- Called when a player right-clicks the node.
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos -- Set global position.
pos_info = pos
local meta = minetest.get_meta(pos)
local player_name = player:get_player_name()
local owner = meta:get_string("owner")
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
-- Show seller interface if the player is the owner.
if player_name == owner then
minetest.show_formspec(player_name, "bank_accounts:card_swipe_seller",
"size[8,9]" ..
@ -60,7 +49,6 @@ minetest.register_node("bank_accounts:card_swipe", {
"list[current_player;main;0,5;8,3]" ..
"button_exit[1,8.25;3,1;reset;"..S("Reset Price").."]" ..
"button_exit[4.5,8.25;3,1;set_price;"..S("Set Price").."]")
-- Show buyer interface for everyone else.
else
if meta:get_inventory():is_empty("items") then
minetest.chat_send_player(player_name, S("[Card Swipe] This machine is empty."))
@ -83,7 +71,7 @@ minetest.register_node("bank_accounts:card_swipe", {
return
end
local price = tonumber(price_str)
local price = normalize_and_tonumber(price_str)
if wielded_item == "bank_accounts:debit_card" and bank_accounts.get_balance(player_name) < price then
minetest.chat_send_player(player_name, S("[Card Swipe] Card declined. Insufficient funds."))
return
@ -91,8 +79,8 @@ minetest.register_node("bank_accounts:card_swipe", {
minetest.show_formspec(player_name, "bank_accounts:card_swipe_buyer",
"size[8,8]" ..
"label[1,1;"..S("Price: @1", string.format("%.2f", price).." MG").."]" ..
"label[1,1.5;"..S("Owner: @1", owner).."]"..
"label[1,1;"..S("Price: @1",string.format("%.2f",price).." MG").."]" ..
"label[1,1.5;"..S("Owner: @1",owner).."]"..
"label[1,2;"..S("Items for Sale (Click 'Buy' to receive):").."]"..
"list["..list_name..";items;0,2.5;8,2]" ..
"list[current_player;main;0,5;8,2;]" ..
@ -101,7 +89,6 @@ minetest.register_node("bank_accounts:card_swipe", {
end
end,
-- Only the owner can put items into the swipe machine.
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if player:get_player_name() == minetest.get_meta(pos):get_string("owner") then
return stack:get_count()
@ -109,14 +96,21 @@ minetest.register_node("bank_accounts:card_swipe", {
return 0
end,
-- THEFT-PREVENTION: Players cannot take items manually.
-- The script transfers them automatically upon successful purchase.
-- KORREKTUR: Nur der Besitzer darf Items wieder aus dem Automaten nehmen.
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local owner = meta:get_string("owner")
local player_name = player:get_player_name()
if player_name == owner or minetest.check_player_privs(player_name, {protection_bypass=true}) then
return stack:get_count()
end
-- Alle anderen (Käufer) werden blockiert.
return 0
end,
})
-- Crafting recipe for the Card Swipe machine.
minetest.register_craft({
output = "bank_accounts:card_swipe",
recipe = {
@ -126,24 +120,16 @@ minetest.register_craft({
},
})
-- Handles formspec submissions for the Card Swipe.
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:card_swipe") then
return
end
local player_name = player:get_player_name()
local pos = pos_info
if not pos then
return
end
if not formname:find("bank_accounts:card_swipe") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local meta = minetest.get_meta(pos)
if not meta then
return
if not meta then return end
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
-- Logic for the seller's interface.
if formname == "bank_accounts:card_swipe_seller" then
if fields.set_price then
local price = normalize_and_tonumber(fields.price)
@ -159,9 +145,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
-- Logic for the buyer's interface.
if formname == "bank_accounts:card_swipe_buyer" then
if fields.buy then
if bank_accounts.is_calculating_interest then on_fail(); return end
local owner = meta:get_string("owner")
local price = tonumber(meta:get_string("price") or "0")
local shop_inv = meta:get_inventory()
@ -176,21 +163,18 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local description_for_buyer = S("Items from @1", owner)
local description_for_seller = S("Items to @1", player_name)
-- Debit Card Transaction
if wielded_item == "bank_accounts:debit_card" then
if bank_accounts.get_balance(player_name) >= price then
bank_accounts.add_balance(player_name, -price, "Purchase", "", description_for_buyer, owner)
bank_accounts.add_balance(owner, price, "Sale", "", description_for_seller, player_name)
payment_successful = true
local s1 = bank_accounts.add_balance(player_name, -price, "Purchase", "", description_for_buyer, owner)
local s2 = bank_accounts.add_balance(owner, price, "Sale", "", description_for_seller, player_name)
if s1 and s2 then payment_successful = true else on_fail() end
end
-- Credit Card Transaction
elseif wielded_item == "bank_accounts:credit_card" then
bank_accounts.add_credit(player_name, price, "Credit Purchase", "", description_for_buyer, owner)
bank_accounts.add_balance(owner, price, "Sale", "", description_for_seller, player_name)
payment_successful = true
local s1 = bank_accounts.add_credit(player_name, price, "Credit Purchase", "", description_for_buyer, owner)
local s2 = bank_accounts.add_balance(owner, price, "Sale", "", description_for_seller, player_name)
if s1 and s2 then payment_successful = true else on_fail() end
end
-- If payment was successful, transfer items.
if payment_successful then
local player_inv = player:get_inventory()
for i=1, shop_inv:get_size("items") do

View file

@ -1,29 +1,21 @@
--[[
Item Definitions
----------------
This file defines all physical items from the mod, like
the different types of cards and the receipt.
--]]
-- cards.lua (mit Übersetzungs-Funktion)
-- The credit card for purchases via credit line.
minetest.register_craftitem("bank_accounts:credit_card", {
description = S("Credit Card"),
description = S("Credit Card"), -- <-- Änderung hier
inventory_image = "credit_card.png",
groups = {not_in_creative_inventory=1},
stack_max = 1,
})
-- The debit card for purchases from the account balance.
minetest.register_craftitem("bank_accounts:debit_card", {
description = S("Debit Card"),
description = S("Debit Card"), -- <-- Änderung hier
inventory_image = "debit_card.png",
groups = {not_in_creative_inventory=1},
stack_max = 1,
})
-- A receipt item given after a card swipe purchase.
minetest.register_craftitem("bank_accounts:receipt", {
description = S("Receipt"),
description = S("Receipt"), -- <-- Änderung hier
inventory_image = "receipt.png",
groups = {not_in_creative_inventory=1},
stack_max = 1,

View file

@ -1,212 +1,62 @@
--[[
Chat Commands
-------------
This file defines all chat commands for players and admins
to interact with the banking system.
--]]
-- chatcommands.lua (Angepasst mit Fehler-Nachricht für besetztes System)
-- Privilege for seized accounts.
minetest.register_privilege("seized", {
description = S("Account seized."),
give_to_singleplayer = false,
})
minetest.register_privilege("seized", { description = S("Account seized."), give_to_singleplayer = false })
-- Allows any player to see their own balance, or privileged players to see others'.
minetest.register_chatcommand("bank_account", {
params = S("[<playername>]"),
description = S("Shows your account balance or the balance of another player (if you have permission)."),
minetest.register_chatcommand("bank_account", { params = S("[<playername>]"), description = S("Shows your account balance or the balance of another player (if you have permission)."),
func = function(name, param)
local target_name = param
if not target_name or target_name == "" then
target_name = name
end
if target_name ~= name and not minetest.check_player_privs(name, {bank_teller=true, server=true}) then
minetest.chat_send_player(name, S("You do not have permission to view other players' accounts."))
return
end
if not bank_accounts.player_has_account(target_name) then
minetest.chat_send_player(name, S("Player '@1' not found or has no account.", target_name))
return
end
local target_name = param; if not target_name or target_name == "" then target_name = name end
if target_name ~= name and not minetest.check_player_privs(name, {bank_teller=true, server=true}) then minetest.chat_send_player(name, S("You do not have permission to view other players' accounts.")); return end
if not bank_accounts.player_has_account(target_name) then minetest.chat_send_player(name, S("Player '@1' not found or has no account.", target_name)); return end
local data = bank_accounts.get_account_data(target_name)
minetest.chat_send_player(name, S("Balance for @1: @2", target_name, string.format("%.2f", data.balance) .. " MG"))
minetest.chat_send_player(name, S("Credit Debt for @1: @2", target_name, string.format("%.2f", data.credit) .. " MG"))
minetest.chat_send_player(name, S("Balance for @1: @2", target_name, string.format("%.2f", data.balance) .. " MG")); minetest.chat_send_player(name, S("Credit Debt for @1: @2", target_name, string.format("%.2f", data.credit) .. " MG"))
end,
})
-- Allows any player to set their own PIN.
minetest.register_chatcommand("bank_set_pin", {
params = S("<4-digit-pin>"),
description = S("Set pin for your bank account."),
minetest.register_chatcommand("bank_set_pin", { params = S("<4-digit-pin>"), description = S("Set pin for your bank account."),
func = function(name, param)
if not param or param == "" then
minetest.chat_send_player(name, S("[Account] No numbers entered."))
return
end
if param:match("^[0-9][0-9][0-9][0-9]$") then
bank_accounts.set_pin(name, param)
minetest.chat_send_player(name, S("[Account] Pin successfully set!"))
local player = minetest.get_player_by_name(name)
if player and not player:get_inventory():contains_item("main", "bank_accounts:atm_card") then
player:get_inventory():add_item("main", "bank_accounts:atm_card")
end
else
minetest.chat_send_player(name, S("[Account] Invalid number entered. Must be exactly 4 digits."))
end
if not param or param == "" then minetest.chat_send_player(name, S("[Account] No numbers entered.")); return end
if param:match("^[0-9][0-9][0-9][0-9]$") then if not bank_accounts.set_pin(name, param) then minetest.chat_send_player(name, S("[Bank] System is busy, please try again in a moment.")) else minetest.chat_send_player(name, S("[Account] Pin successfully set!")) end
else minetest.chat_send_player(name, S("[Account] Invalid number entered. Must be exactly 4 digits.")) end
end
})
-- Helper function to parse admin commands that require an integer amount.
local function parse_admin_params_int(params)
local parts = params:split(" ", 2)
local playername = parts[1]
local number_str = parts[2]
if not playername or playername=="" then return nil, nil, S("[Bank] Player name missing.") end
if not bank_accounts.player_has_account(playername) then return nil, nil, S("[Bank] Invalid player name entered.") end
if not number_str or number_str=="" then return playername, nil, S("[Bank] No number entered.") end
local number = normalize_and_tonumber(number_str)
if not number then return playername, nil, S("[Bank] Invalid number entered.") end
if number % 1 ~= 0 then return playername, nil, S("[Bank] Amount must be a whole number.") end
return playername, number
end
local function parse_admin_params_int(params) local parts=params:split(" ",2);local playername=parts[1];local number_str=parts[2];if not playername or playername=="" then return nil,nil,S("[Bank] Player name missing.") end;if not bank_accounts.player_has_account(playername) then return nil,nil,S("[Bank] Invalid player name entered.") end;if not number_str or number_str=="" then return playername,nil,S("[Bank] No number entered.") end;local number=normalize_and_tonumber(number_str);if not number then return playername,nil,S("[Bank] Invalid number entered.") end;if number % 1 ~= 0 then return playername,nil,S("[Bank] Amount must be a whole number.") end;return playername,number end
local function parse_admin_params_float(params) local parts=params:split(" ",2);local playername=parts[1];local number_str=parts[2];if not playername or playername=="" then return nil,nil,S("[Bank] Player name missing.") end;if not bank_accounts.player_has_account(playername) then return nil,nil,S("[Bank] Invalid player name entered.") end;if not number_str or number_str=="" then return playername,nil,S("[Bank] No number entered.") end;local number=normalize_and_tonumber(number_str);if not number then return playername,nil,S("[Bank] Invalid number entered.") end;return playername,number end
-- Helper function to parse admin commands that allow a float amount.
local function parse_admin_params_float(params)
local parts = params:split(" ", 2)
local playername = parts[1]
local number_str = parts[2]
if not playername or playername=="" then return nil, nil, S("[Bank] Player name missing.") end
if not bank_accounts.player_has_account(playername) then return nil, nil, S("[Bank] Invalid player name entered.") end
if not number_str or number_str=="" then return playername, nil, S("[Bank] No number entered.") end
local number = normalize_and_tonumber(number_str)
if not number then return playername, nil, S("[Bank] Invalid number entered.") end
return playername, number
end
local function on_fail(name) minetest.chat_send_player(name, S("[Bank] System is busy, please try again in a moment.")) end
-- Adds an integer amount to a player's balance. (Admin only)
minetest.register_chatcommand("bank_add", {
params = S("<name> <number>"),
description = S("Add to a player's account balance."),
privs = {server = true},
minetest.register_chatcommand("bank_add", { params = S("<name> <number>"), description = S("Add to a player's account balance."), privs = {server = true},
func = function(name, params)
local target_name, amount, err = parse_admin_params_int(params)
if err then
minetest.chat_send_player(name, err)
return
end
if not amount or amount <= 0 then
minetest.chat_send_player(name, S("[Bank] Number must be greater than 0."))
return
end
bank_accounts.add_balance(target_name, amount, "Admin Deposit", S("By Admin: @1", name), name)
minetest.chat_send_player(name, S("[Bank] @1 successfully added to @2's account.", tostring(amount).." MG", target_name))
local target_name, amount, err = parse_admin_params_int(params); if err then minetest.chat_send_player(name, err); return end
if not amount or amount <= 0 then minetest.chat_send_player(name, S("[Bank] Number must be greater than 0.")); return end
if not bank_accounts.add_balance(target_name, amount, "Admin Deposit", S("By Admin: @1", name), name) then on_fail(name) else minetest.chat_send_player(name, S("[Bank] @1 successfully added to @2's account.", tostring(amount).." MG", target_name)) end
end
})
-- Subtracts an integer amount from a player's balance. (Admin only)
minetest.register_chatcommand("bank_subtract", {
params = S("<name> <number>"),
description = S("Subtract from a player's account balance."),
privs = {server = true},
minetest.register_chatcommand("bank_subtract", { params = S("<name> <number>"), description = S("Subtract from a player's account balance."), privs = {server = true},
func = function(name, params)
local target_name, amount, err = parse_admin_params_int(params)
if err then
minetest.chat_send_player(name, err)
return
end
if not amount or amount <= 0 then
minetest.chat_send_player(name, S("[Bank] Number must be greater than 0."))
return
end
bank_accounts.add_balance(target_name, -amount, "Admin Withdrawal", S("By Admin: @1", name), name)
minetest.chat_send_player(name, S("[Bank] @1 successfully subtracted from @2's account.", tostring(amount).." MG", target_name))
local target_name, amount, err = parse_admin_params_int(params); if err then minetest.chat_send_player(name, err); return end
if not amount or amount <= 0 then minetest.chat_send_player(name, S("[Bank] Number must be greater than 0.")); return end
if not bank_accounts.add_balance(target_name, -amount, "Admin Withdrawal", S("By Admin: @1", name), name) then on_fail(name) else minetest.chat_send_player(name, S("[Bank] @1 successfully subtracted from @2's account.", tostring(amount).." MG", target_name)) end
end
})
-- Sets a player's balance to a specific (float) amount. (Admin only)
minetest.register_chatcommand("bank_balance", {
params = S("<name> <number>"),
description = S("Set a player's account balance."),
privs = {server = true},
minetest.register_chatcommand("bank_balance", { params = S("<name> <number>"), description = S("Set a player's account balance."), privs = {server = true},
func = function(name, params)
local target_name, amount, err = parse_admin_params_float(params)
if err then
minetest.chat_send_player(name, err)
return
end
if amount == nil or amount < 0 then
minetest.chat_send_player(name, S("[Bank] Number must be 0 or greater."))
return
end
bank_accounts.set_balance(target_name, amount, "Admin Set Balance", S("By Admin: @1", name), name)
minetest.chat_send_player(name, S("[Bank] Funds successfully set for @1.", target_name))
local target_name, amount, err = parse_admin_params_float(params); if err then minetest.chat_send_player(name, err); return end
if amount == nil or amount < 0 then minetest.chat_send_player(name, S("[Bank] Number must be 0 or greater.")); return end
if not bank_accounts.set_balance(target_name, amount, "Admin Set Balance", S("By Admin: @1", name), name) then on_fail(name) else minetest.chat_send_player(name, S("[Bank] Funds successfully set for @1.", target_name)) end
end
})
-- Sets a player's credit debt to a specific (float) amount. (Admin only)
minetest.register_chatcommand("bank_credit", {
params = S("<name> <number>"),
description = S("Set a player's credit debt."),
privs = {server = true},
minetest.register_chatcommand("bank_credit", { params = S("<name> <number>"), description = S("Set a player's credit debt."), privs = {server = true},
func = function(name, params)
local target_name, amount, err = parse_admin_params_float(params)
if err then
minetest.chat_send_player(name, err)
return
end
if amount == nil or amount < 0 then
minetest.chat_send_player(name, S("[Bank] Number must be 0 or greater."))
return
end
bank_accounts.set_credit(target_name, amount, "Admin Set Credit", S("By Admin: @1", name), name)
minetest.chat_send_player(name, S("[Bank] Credit debt for @1 has been set to @2.", string.format("%.2f", amount).." MG", target_name))
local target_name, amount, err = parse_admin_params_float(params); if err then minetest.chat_send_player(name, err); return end
if amount == nil or amount < 0 then minetest.chat_send_player(name, S("[Bank] Number must be 0 or greater.")); return end
if not bank_accounts.set_credit(target_name, amount, "Admin Set Credit", S("By Admin: @1", name), name) then on_fail(name) else minetest.chat_send_player(name, S("[Bank] Credit debt for @1 has been set to @2.", string.format("%.2f", amount).." MG", target_name)) end
end
})
-- Wipes a player's balance to 0. (Admin only)
minetest.register_chatcommand("bank_wipe", {
params = S("<name>"),
description = S("Wipe a player's bank account."),
privs = {server = true},
func = function(name, param)
if not param or not bank_accounts.player_has_account(param) then
minetest.chat_send_player(name, S("[Bank] Invalid player name entered."))
return
end
bank_accounts.set_balance(param, 0, "Admin Wipe", S("By Admin: @1", name), name)
minetest.chat_send_player(name, S("[Bank] Account successfully wiped for @1!", param))
end
})
-- Seizes a player's account. (Admin only)
minetest.register_chatcommand("bank_seize", {
params = S("<name>"),
description = S("Seize a player's account."),
privs = {server = true},
func = function(name, param)
if not param or not bank_accounts.player_has_account(param) then
minetest.chat_send_player(name, S("[Bank] Invalid player name entered."))
return
end
minetest.set_player_privs(param, {seized = true})
minetest.chat_send_player(name, S("[Bank] Account successfully seized for @1!", param))
end
})
-- Unseizes a player's account. (Admin only)
minetest.register_chatcommand("bank_unseize", {
params = S("<name>"),
description = S("Unseize a player's account."),
privs = {server = true},
func = function(name, param)
if not param or not bank_accounts.player_has_account(param) then
minetest.chat_send_player(name, S("[Bank] Invalid player name entered."))
return
end
minetest.set_player_privs(param, {seized = nil})
minetest.chat_send_player(name, S("[Bank] Account successfully unseized for @1!", param))
end
})
minetest.register_chatcommand("bank_wipe", { params = S("<name>"), description = S("Wipe a player's bank account."), privs = {server = true}, func = function(name, param) if not param or not bank_accounts.player_has_account(param) then minetest.chat_send_player(name, S("[Bank] Invalid player name entered.")); return end; if not bank_accounts.set_balance(param, 0, "Admin Wipe", S("By Admin: @1", name), name) then on_fail(name) else minetest.chat_send_player(name, S("[Bank] Account successfully wiped for @1!", param)) end end })
minetest.register_chatcommand("bank_seize", { params = S("<name>"), description = S("Seize a player's account."), privs = {server = true}, func = function(name, param) if not param or not bank_accounts.player_has_account(param) then minetest.chat_send_player(name, S("[Bank] Invalid player name entered.")); return end; minetest.set_player_privs(param, {seized = true}); minetest.chat_send_player(name, S("[Bank] Account successfully seized for @1!", param)) end })
minetest.register_chatcommand("bank_unseize", { params = S("<name>"), description = S("Unseize a player's account."), privs = {server = true}, func = function(name, param) if not param or not bank_accounts.player_has_account(param) then minetest.chat_send_player(name, S("[Bank] Invalid player name entered.")); return end; minetest.set_player_privs(param, {seized = nil}); minetest.chat_send_player(name, S("[Bank] Account successfully unseized for @1!", param)) end })

View file

@ -1,17 +1,11 @@
--[[
Teller Computer Node
--------------------
This file defines the Teller Computer node, which allows players with
the 'bank_teller' privilege to manage other players' accounts.
--]]
-- computer.lua (Zurückgesetzt auf stabile Basis + Nur "System is busy" Logik hinzugefügt)
-- Uses the global 'pos_info' variable defined in atm.lua for consistency.
pos_data = {} -- Kept for historical reasons, but pos_info is used.
minetest.register_privilege("bank_teller", {
description = S("Qualified Bank Teller"),
})
pos_data = {}
-- Shows the main form for the teller.
-- @param player: The teller player object.
-- @param pos: The position of the computer node.
-- @param customer_name: The name of the customer being managed.
local function show_teller_form(player, pos, customer_name)
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
local data = {balance = 0, credit = 0}
@ -24,242 +18,118 @@ local function show_teller_form(player, pos, customer_name)
"list["..list_name..";ones;0,.25;1,1]" .. "list["..list_name..";fives;1.25,.25;1,1]" .. "list["..list_name..";tens;2.5,.25;1,1]" ..
"label[0.25,1.2;1 MG]" .. "label[1.5,1.2;5 MG]" .. "label[2.75,1.2;10 MG]" ..
"field[4,.5;4,1;playername;"..S("Player:")..";"..minetest.formspec_escape(customer_name or "").."]" ..
"field[.3,2.25;4,1;withdrawal;"..S("Withdraw:")..";]" ..
"field[.3,3.25;4,1;credit_debt;"..S("Credit Payment:")..";]" ..
"field[.3,2.25;4,1;withdrawal;"..S("Withdraw:")..";]" .. "field[.3,3.25;4,1;credit_debt;"..S("Credit Payment:")..";]" ..
"label[.5,4.5;"..S("Balance: @1", string.format("%.2f", data.balance).." MG").."]" ..
"label[.5,4.75;"..S("Credit Debt: @1", string.format("%.2f", data.credit).." MG").."]" ..
"button_exit[5.5,1;2,1;stats;"..S("Show Account").."]" ..
"button_exit[5.5,2;2,1;statement;"..S("Account Statement").."]" ..
"button_exit[5.5,3;2,1;wipe;"..S("Wipe Account").."]" ..
"button_exit[5.5,4;2,1;reset_pin;"..S("Reset PIN").."]" ..
"button_exit[3,5.5;2,1;exit;"..S("Cancel").."]" ..
"button_exit[5.5,5.5;2,1;enter;"..S("Enter").."]" ..
"button_exit[5.5,3;2,1;wipe;"..S("Wipe Account").."]" .. "button_exit[5.5,4;2,1;reset_pin;"..S("Reset PIN").."]" ..
"button_exit[3,5.5;2,1;exit;"..S("Cancel").."]" .. "button_exit[5.5,5.5;2,1;enter;"..S("Enter").."]" ..
"list[current_player;main;0,7;8,2.5;]"
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller", formspec)
end
-- Shows the account statement form for a given customer.
local function show_statement_form(player, pos, customer_name, account_type)
local data = bank_accounts.get_account_data(customer_name)
local history = data.history
local lines = {}
local current_total_label = ""
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then
for _, t in ipairs(history) do
if t.account == account_type then
local parts = {}
table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp)))
table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG"))
table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG"))
table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = t.purpose or ""
local partner = t.other or ""
if purpose ~= "" or partner ~= "" then
table.insert(parts, string.format("%-20s", purpose))
if partner ~= "" then
table.insert(parts, partner)
end
end
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = t.purpose or ""; local partner = t.other or ""; if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end
end
end
if account_type == "balance" then
current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance).." MG")
else
current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit).." MG")
end
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance).." MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit).." MG") end
local form_name = "bank_accounts:statement@" .. customer_name
local formspec = "size[13,9]" ..
"label[0,0;"..S("Account Statement for @1",customer_name).."]" ..
"button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]" ..
"button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]" ..
"textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]"..
"label[0,8.4;"..current_total_label.."]"..
"button_exit[11,8.4;2,1;back;"..S("Back").."]"
local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1",customer_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player:get_player_name(), form_name, formspec)
end
-- Node definition for the Teller Computer.
minetest.register_node("bank_accounts:teller_computer", {
description = S("Bank Teller's Computer"),
drawtype = "mesh",
mesh = "computer.obj",
paramtype = "light",
paramtype2 = "facedir",
light_source = 5,
tiles = { {name="computer.png"},{name="computer_screen.png"} },
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
selection_box = { type = "fixed", fixed = {{-.5,-.5,-.5,.5,.4,.2}} },
collision_box = { type = "fixed", fixed = {{-.5,-.5,-.5,.5,.4,.2}} },
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size("ones", 1)
inv:set_size("fives", 1)
inv:set_size("tens", 1)
end,
drawtype = "mesh", mesh = "computer.obj", paramtype = "light", paramtype2 = "facedir", light_source = 5,
tiles = {{name="computer.png"},{name="computer_screen.png"}}, groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
selection_box = {type="fixed",fixed={-0.5,-0.5,-0.5,0.5,0.4,0.2}}, collision_box = {type="fixed",fixed={-0.5,-0.5,-0.5,0.5,0.4,0.2}},
on_construct = function(pos) local meta = minetest.get_meta(pos); local inv = meta:get_inventory(); inv:set_size("ones", 1); inv:set_size("fives", 1); inv:set_size("tens", 1) end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
local player_name = player:get_player_name()
if player:get_player_control().aux1 and minetest.check_player_privs(player_name, {server=true}) then
minetest.show_formspec(player_name, "bank_accounts:admin_teller",
"size[8,4]" ..
"field[.5,.5;4,1;search;"..S("Search:")..";]" ..
"button_exit[4.5,.22;2,1;search_button;"..S("Search").."]" ..
"label[.5,1.25;"..S("Player:").."]".. "label[3.5,1.25;"..S("Seized:").."]")
elseif minetest.check_player_privs(player_name, {bank_teller=true}) then
show_teller_form(player, pos, "")
else
minetest.chat_send_player(player_name, S("[Bank] Insufficient privileges."))
end
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname=="ones" and stack:get_name()~="currency:minegeld" then return 0 end
if listname=="fives" and stack:get_name()~="currency:minegeld_5" then return 0 end
if listname=="tens" and stack:get_name()~="currency:minegeld_10" then return 0 end
return stack:get_count()
end,
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
local inv=minetest.get_meta(pos):get_inventory()
local stack=inv:get_stack(from_list, from_index)
if to_list=="ones" and stack:get_name()~="currency:minegeld" then return 0 end
if to_list=="fives" and stack:get_name()~="currency:minegeld_5" then return 0 end
if to_list=="tens" and stack:get_name()~="currency:minegeld_10" then return 0 end
return count
if player:get_player_control().aux1 and minetest.check_player_privs(player_name, {server=true}) then minetest.show_formspec(player_name, "bank_accounts:admin_teller", "size[8,4]" .. "field[.5,.5;4,1;search;"..S("Search:")..";]" .. "button_exit[4.5,.22;2,1;search_button;"..S("Search").."]" .. "label[.5,1.25;"..S("Player:").."]".. "label[3.5,1.25;"..S("Seized:").."]")
elseif minetest.check_player_privs(player_name, {bank_teller=true}) then show_teller_form(player, pos, "")
else minetest.chat_send_player(player_name, S("[Bank] Insufficient privileges.")) end
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player) if listname=="ones" and stack:get_name()~="currency:minegeld" then return 0 end; if listname=="fives" and stack:get_name()~="currency:minegeld_5" then return 0 end; if listname=="tens" and stack:get_name()~="currency:minegeld_10" then return 0 end; return stack:get_count() end,
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) local inv=minetest.get_meta(pos):get_inventory(); local stack=inv:get_stack(from_list, from_index); if to_list=="ones" and stack:get_name()~="currency:minegeld" then return 0 end; if to_list=="fives" and stack:get_name()~="currency:minegeld_5" then return 0 end; if to_list=="tens" and stack:get_name()~="currency:minegeld_10" then return 0 end; return count end,
})
-- Central handler for all Teller and Admin forms.
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:teller") and not formname:find("bank_accounts:admin_teller") and not formname:find("bank_accounts:statement") then
return
if not formname:find("bank_accounts:teller") and not formname:find("bank_accounts:admin_teller") and not formname:find("bank_accounts:statement") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
local player_name = player:get_player_name()
local pos = pos_info
if not pos then
return
end
-- Admin search form.
if formname == "bank_accounts:admin_teller" then
if fields.search_button and fields.search and fields.search ~= "" then
local search_name=fields.search
local status
if not bank_accounts.player_has_account(search_name) then
status=S("No Account")
elseif minetest.check_player_privs(search_name,{seized=true}) then
status=S("Yes")
else
status=S("No")
end
minetest.show_formspec(player_name,formname,
"size[8,4]"..
"field[.5,.5;4,1;search;"..S("Search:")..";"..minetest.formspec_escape(search_name).."]"..
"button_exit[4.5,.22;2,1;search_button;"..S("Search").."]"..
"label[.5,1.25;"..S("Player:").."]".."label[3.5,1.25;"..S("Seized:").."]"..
"label[.5,1.75;"..minetest.formspec_escape(search_name).."]".."label[3.5,1.75;"..status.."]")
end
return
local search_name=fields.search; local status; if not bank_accounts.player_has_account(search_name) then status=S("No Account") elseif minetest.check_player_privs(search_name,{seized=true}) then status=S("Yes") else status=S("No") end; minetest.show_formspec(player_name,formname,"size[8,4]".."field[.5,.5;4,1;search;"..S("Search:")..";"..minetest.formspec_escape(search_name).."]".."button_exit[4.5,.22;2,1;search_button;"..S("Search").."]".."label[.5,1.25;"..S("Player:").."]".."label[3.5,1.25;"..S("Seized:").."]".."label[.5,1.75;"..minetest.formspec_escape(search_name).."]".."label[3.5,1.75;"..status.."]")
end; return
end
-- Main teller form.
if formname == "bank_accounts:teller" then
local customer_name = fields.playername
-- This validation logic is quirky but proven to be stable in the user's environment.
if (not customer_name or customer_name=="") and not fields.exit then
minetest.chat_send_player(player_name, S("[Bank] Must enter a player name."))
return
end
if (customer_name and customer_name~="" and not bank_accounts.player_has_account(customer_name)) and not fields.exit then
minetest.chat_send_player(player_name, S("[Bank] Invalid player name entered."))
return
-- Die "seltsame", aber für dich funktionierende Validierungslogik. Unverändert.
if (not customer_name or customer_name=="") and not fields.exit then minetest.chat_send_player(player_name, S("[Bank] Must enter a player name.")); return end
if (customer_name and customer_name~="" and not bank_accounts.player_has_account(customer_name)) and not fields.exit then minetest.chat_send_player(player_name, S("[Bank] Invalid player name entered.")); return end
-- Funktions-Wrapper beibehalten, da er in der funktionierenden Basis war.
local function show_updated_form(p, a_pos, c_name)
show_teller_form(p, a_pos, c_name)
end
if fields.stats then
show_teller_form(player, pos, customer_name)
show_updated_form(player, pos, customer_name)
elseif fields.statement then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.wipe then
bank_accounts.set_balance(customer_name, 0, "Teller Wipe", S("Teller: @1", player_name), player_name)
show_teller_form(player, pos, customer_name)
if not bank_accounts.set_balance(customer_name, 0, "Teller Wipe", S("Teller: @1", player_name), player_name) then on_fail() end
show_updated_form(player, pos, customer_name)
elseif fields.reset_pin then
bank_accounts.set_pin(customer_name, "0000")
show_teller_form(player, pos, customer_name)
if not bank_accounts.set_pin(customer_name, "0000") then on_fail() end
show_updated_form(player, pos, customer_name)
elseif fields.enter then
local meta=minetest.get_meta(pos)
local inv=meta:get_inventory()
-- Deposit
local meta=minetest.get_meta(pos); local inv=meta:get_inventory();
local total_deposit=inv:get_stack("ones",1):get_count()+inv:get_stack("fives",1):get_count()*5+inv:get_stack("tens",1):get_count()*10
if total_deposit>0 then
bank_accounts.add_balance(customer_name,total_deposit,"Teller Deposit",S("Teller: @1",player_name),player_name)
if not bank_accounts.add_balance(customer_name,total_deposit,"Teller Deposit",S("Teller: @1",player_name),player_name) then on_fail() end
end
-- Withdrawal
local withdrawal = tonumber(fields.withdrawal)
if withdrawal and withdrawal>0 then
if withdrawal%1~=0 then
minetest.chat_send_player(player_name,S("[Bank] Withdrawal amount must be a whole number."))
local withdrawal = tonumber(fields.withdrawal); if withdrawal and withdrawal>0 then
if withdrawal%1~=0 then minetest.chat_send_player(player_name,S("[Bank] Withdrawal amount must be a whole number."))
elseif bank_accounts.get_balance(customer_name)>=withdrawal then
bank_accounts.add_balance(customer_name,-withdrawal,"Teller Withdrawal",S("Teller: @1",player_name),player_name)
local tens=math.floor(withdrawal/10);local rem=withdrawal%10;local fives=math.floor(rem/5);local ones=rem%5
player:get_inventory():add_item("main",{name="currency:minegeld_10",count=tens})
player:get_inventory():add_item("main",{name="currency:minegeld_5",count=fives})
player:get_inventory():add_item("main",{name="currency:minegeld",count=ones})
else
minetest.chat_send_player(player_name,S("[Bank] Player has insufficient funds for withdrawal."))
if not bank_accounts.add_balance(customer_name,-withdrawal,"Teller Withdrawal",S("Teller: @1",player_name),player_name) then on_fail()
else local tens=math.floor(withdrawal/10);local rem=withdrawal%10;local fives=math.floor(rem/5);local ones=rem%5;player:get_inventory():add_item("main",{name="currency:minegeld_10",count=tens});player:get_inventory():add_item("main",{name="currency:minegeld_5",count=fives});player:get_inventory():add_item("main",{name="currency:minegeld",count=ones}) end
else minetest.chat_send_player(player_name,S("[Bank] Player has insufficient funds for withdrawal.")) end
end
end
-- Credit Payment
local credit_payment = normalize_and_tonumber(fields.credit_debt)
if credit_payment and credit_payment>0 then
local credit_payment = normalize_and_tonumber(fields.credit_debt); if credit_payment and credit_payment>0 then
if bank_accounts.get_balance(customer_name)>=credit_payment then
if bank_accounts.get_credit(customer_name)>=credit_payment then
bank_accounts.add_balance(customer_name,-credit_payment,"Teller Credit Payment",S("Teller: @1",player_name),player_name)
bank_accounts.add_credit(customer_name,-credit_payment,"Teller Credit Payment",S("Teller: @1",player_name),player_name)
else
minetest.chat_send_player(player_name,S("[Bank] Player does not have that much credit debt."))
local s1 = bank_accounts.add_balance(customer_name,-credit_payment,"Teller Credit Payment",S("Teller: @1",player_name),player_name)
local s2 = bank_accounts.add_credit(customer_name,-credit_payment,"Teller Credit Payment",S("Teller: @1",player_name),player_name)
if not s1 or not s2 then on_fail() end
else minetest.chat_send_player(player_name,S("[Bank] Player does not have that much credit debt.")) end
else minetest.chat_send_player(player_name,S("[Bank] Player has insufficient funds for credit payment.")) end
end
else
minetest.chat_send_player(player_name,S("[Bank] Player has insufficient funds for credit payment."))
inv:set_stack("ones",1,nil);inv:set_stack("fives",1,nil);inv:set_stack("tens",1,nil);
show_updated_form(player,pos,customer_name)
end
end
inv:set_stack("ones",1,nil)
inv:set_stack("fives",1,nil)
inv:set_stack("tens",1,nil)
show_teller_form(player,pos,customer_name)
end
end
-- Statement form.
if formname:find("bank_accounts:statement@") then
local customer_name=formname:match("bank_accounts:statement@(.*)")
if not customer_name then
return
end
if fields.view_balance then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.view_credit then
show_statement_form(player, pos, customer_name, "credit")
elseif fields.back then
show_teller_form(player, pos, customer_name)
end
local customer_name=formname:match("bank_accounts:statement@(.*)"); if not customer_name then return end
if fields.view_balance then show_statement_form(player, pos, customer_name, "balance")
elseif fields.view_credit then show_statement_form(player, pos, customer_name, "credit")
elseif fields.back then show_teller_form(player, pos, customer_name) end
end
end)

309
computer2.lua Normal file
View file

@ -0,0 +1,309 @@
-- computer2.lua (Added screen texture)
-- Uses the global 'pos_info' variable for consistency.
-- This table will temporarily store which customer a teller is currently serving for the deposit form.
local teller_current_customer = {}
-- Copy currency definitions and helper functions from atm2.lua
local currency_values = {
{name = "currency:minegeld_100", value = 100},
{name = "currency:minegeld_50", value = 50},
{name = "currency:minegeld_10", value = 10},
{name = "currency:minegeld_5", value = 5},
{name = "currency:minegeld", value = 1},
{name = "currency:minegeld_cent_25", value = 0.25},
{name = "currency:minegeld_cent_10", value = 0.10},
{name = "currency:minegeld_cent_5", value = 0.05},
}
local function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
local function get_item_value(itemstack)
local name = itemstack:get_name(); for _, c in ipairs(currency_values) do if c.name == name then return c.value end end; return 0
end
local function amount_to_itemstacks(amount)
local items = {}; local rem = amount; for _, c in ipairs(currency_values) do if rem >= c.value then local count = math.floor(rem / c.value); if count > 0 then table.insert(items, {name=c.name, count=count}); rem = rem - (count*c.value); rem = round(rem, 2); if rem < 0.001 then break end end end end; return items
end
-- Formspec for the initial customer selection screen
local function show_teller2_selection_form(player, pos, search_term)
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, balance in pairs(all_accounts.balance) do
if not search_term or search_term == "" or name:lower():find(search_term:lower(), 1, true) then
table.insert(customer_list, minetest.formspec_escape(name))
end
end
table.sort(customer_list)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_select",
"size[8,9.5]"..
"label[0,0;"..S("Select Customer Account").."]"..
"field[0.29,0.85;5.8,1;search_name;;"..minetest.formspec_escape(search_term or "").."]"..
"button_exit[6.2,0.55;1.8,1;search;"..S("Search").."]"..
"table[0,1.8;8,7;customer_list;"..table.concat(customer_list, ",").."]"
)
end
-- Formspec for viewing and managing a selected customer's account
local function show_teller2_customer_view(player, pos, customer_name)
local data = bank_accounts.get_account_data(customer_name)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_view@"..customer_name,
"size[8,9]"..
"label[0,0;"..S("Managing Account: @1", customer_name).."]"..
"label[0.5,1;"..S("Balance: @1", string.format("%.2f", data.balance).." MG").."]"..
"label[4.5,1;"..S("Credit Debt: @1", string.format("%.2f", data.credit).." MG").."]"..
"container[0,0.5;8,1.25]"..
"container_end[]"..
"button_exit[0.5,2.5;3,1;deposit;"..S("Deposit").."]"..
"button_exit[4.5,2.5;3,1;withdrawal;"..S("Withdrawal").."]"..
"button_exit[0.5,4;3,1;pay_credit;"..S("Pay Credit Debt").."]"..
"button_exit[4.5,4;3,1;statement;"..S("Account Statement").."]"..
"button_exit[0.5,5.5;3,1;reset_pin;"..S("Reset PIN").."]"..
"button_exit[4.5,5.5;3,1;wipe;"..S("Wipe Account").."]"..
"button_exit[0.5,7;3,1;back_to_search;"..S("Back to Customer List").."]"
)
end
-- Formspec for the new deposit form
local function show_teller2_deposit_form(player, pos, customer_name)
local meta = minetest.get_meta(pos)
local deposited_amount = tonumber(meta:get_string("deposit_buffer") or "0")
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_deposit@"..customer_name,
"size[8,9]"..
"label[0,0.5;"..S("Deposit for @1", customer_name).."]"..
"label[0,1.5;"..S("Place coins and notes in the slot below.").."]"..
"list["..list_name..";deposit_slot;3.5,2.5;1,1;]"..
"label[0,4;"..S("Currently Deposited: @1 MG", string.format("%.2f", deposited_amount)).."]"..
"button_exit[0.5,5;3,1;confirm_deposit;"..S("Deposit").."]"..
"button_exit[4.5,5;3,1;return_deposit;"..S("Return").."]"..
"list[current_player;main;0,6;8,3;]")
end
-- Formspec for the account statement
local function show_statement_form(player, pos, customer_name, account_type)
local data = bank_accounts.get_account_data(customer_name)
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = t.purpose or ""; local partner = t.other or ""; if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance).." MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit).." MG") end
local form_name = "bank_accounts:teller2_statement@" .. customer_name
local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1",customer_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player:get_player_name(), form_name, formspec)
end
minetest.register_node("bank_accounts:teller_computer2", {
description = S("Teller Computer Mark II"),
drawtype = "mesh",
mesh = "computer.obj",
paramtype = "light",
paramtype2 = "facedir",
-- KORREKTUR: Bildschirm-Textur hinzugefügt
tiles = {
{name="computer2.png"},
{name="computer2_screen.png"},
},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
on_construct = function(pos)
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
inv:set_size("deposit_slot", 1)
for _, currency in ipairs(currency_values) do inv:set_size(currency.name, 1) end
end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
if minetest.check_player_privs(player:get_player_name(), {bank_teller=true, server=true}) then
show_teller2_selection_form(player, pos, "")
else
minetest.chat_send_player(player:get_player_name(), S("[Bank] Insufficient privileges."))
end
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname ~= "deposit_slot" then return end
local value = get_item_value(stack)
if value > 0 then
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
local current_buffer = tonumber(meta:get_string("deposit_buffer") or "0")
meta:set_string("deposit_buffer", tostring(current_buffer + value * stack:get_count()))
inv:set_stack(listname, index, "")
local customer_name = teller_current_customer[player:get_player_name()]
if customer_name then
minetest.after(0, show_teller2_deposit_form, player, pos, customer_name)
end
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:teller2_") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local meta = minetest.get_meta(pos)
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
-- Customer Selection Form
if formname == "bank_accounts:teller2_select" then
local customer_name = fields.search_name or ""
if fields.customer_list then
local event = minetest.explode_table_event(fields.customer_list)
if event.type == "CHG" or event.type == "DCL" then
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, _ in pairs(all_accounts.balance) do
if not fields.search_name or fields.search_name == "" or name:lower():find(fields.search_name:lower(), 1, true) then
table.insert(customer_list, name)
end
end
table.sort(customer_list)
customer_name = customer_list[event.row]
end
end
if customer_name and bank_accounts.player_has_account(customer_name) then
show_teller2_customer_view(player, pos, customer_name)
else
show_teller2_selection_form(player, pos, fields.search_name)
end
-- Customer View Form
elseif formname:find("bank_accounts:teller2_view@") then
local customer_name = formname:match("bank_accounts:teller2_view@(.*)")
if not customer_name then return end
if fields.back_to_search then
show_teller2_selection_form(player, pos, "")
elseif fields.statement then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.reset_pin then
minetest.show_formspec(player_name, "bank_accounts:teller2_reset_pin_confirm@"..customer_name, "size[8,4]".."label[1,1;"..S("Really reset PIN for @1?", customer_name).."]".."button_exit[1,3;2.5,1;yes_reset;"..S("Yes").."]".."button_exit[4.5,3;2.5,1;no;"..S("No").."]")
elseif fields.wipe then
minetest.show_formspec(player_name, "bank_accounts:teller2_wipe_confirm@"..customer_name, "size[8,4]".."label[1,1;"..S("Really wipe account for @1?", customer_name).."]".."button_exit[1,3;2.5,1;yes_wipe;"..S("Yes").."]".."button_exit[4.5,3;2.5,1;no;"..S("No").."]")
elseif fields.deposit then
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = customer_name
show_teller2_deposit_form(player, pos, customer_name)
elseif fields.withdrawal then
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal@"..customer_name, "size[8,8]" .. "field[2,4;5,1;money;"..S("Amount:")..";]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]")
elseif fields.pay_credit then
local credit_debt = bank_accounts.get_credit(customer_name)
local next_rate = 0
if credit_debt > 0 then next_rate = math.max(1, math.floor(credit_debt * 0.04)) end
minetest.show_formspec(player_name, "bank_accounts:teller2_pay_credit@"..customer_name, "size[8,8]" .. "label[0,0.5;"..S("Total Credit Debt: @1", string.format("%.2f", credit_debt).." MG").."]".. "label[0,2;"..S("Next calculated rate:").."]".. "button_exit[0,2.5;3.5,1;pay_rate;"..S("Pay Next Rate (@1 MG)", next_rate).."]" .. "label[4.2,2;"..S("Or enter a custom amount:").."]" .. "field[4.5,2.8;3.5,1;custom_amount;"..""..";]" .. "button_exit[4.2,3.5;3.5,1;pay_custom;"..S("Pay Custom Amount").."]" .. "button_exit[3,7;2,1;exit;"..S("Cancel").."]")
end
-- Confirmation Forms
elseif formname:find("bank_accounts:teller2_reset_pin_confirm@") then
local customer_name = formname:match("bank_accounts:teller2_reset_pin_confirm@(.*)")
if fields.yes_reset then
if not bank_accounts.set_pin(customer_name, "0000") then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Player's pin successfully reset!")) end
end
show_teller2_customer_view(player, pos, customer_name)
elseif formname:find("bank_accounts:teller2_wipe_confirm@") then
local customer_name = formname:match("bank_accounts:teller2_wipe_confirm@(.*)")
if fields.yes_wipe then
if not bank_accounts.set_balance(customer_name, 0, "Teller Wipe", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Account successfully wiped!")) end
end
show_teller2_customer_view(player, pos, customer_name)
-- Deposit Form
elseif formname:find("bank_accounts:teller2_deposit@") then
local customer_name = formname:match("bank_accounts:teller2_deposit@(.*)")
local buffer = tonumber(meta:get_string("deposit_buffer") or "0")
if fields.confirm_deposit then
if buffer > 0 then
if not bank_accounts.add_balance(customer_name, buffer, "Teller Deposit", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Deposited @1 for @2.", string.format("%.2f", buffer), customer_name)) end
end
elseif fields.return_deposit or fields.quit then
if buffer > 0 then
local items_to_return = amount_to_itemstacks(buffer)
for _, item in ipairs(items_to_return) do player:get_inventory():add_item("main", item) end
if fields.quit then minetest.chat_send_player(player_name, S("[Bank] The deposit was cancelled and returned to your inventory.")) end
end
end
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = nil
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Withdrawal Forms
elseif formname:find("bank_accounts:teller2_withdrawal@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal@(.*)")
if fields.enter then
local requested_amount = normalize_and_tonumber(fields.money)
if requested_amount and requested_amount > 0 and bank_accounts.get_balance(customer_name) >= requested_amount then
local items_to_dispense = amount_to_itemstacks(requested_amount)
local dispensable_amount = 0
for _, item_data in ipairs(items_to_dispense) do for _, currency in ipairs(currency_values) do if currency.name == item_data.name then dispensable_amount = dispensable_amount + (currency.value * item_data.count); break end end end
if not bank_accounts.add_balance(customer_name, -dispensable_amount, "Teller Withdrawal", S("Teller: @1", player_name), player_name) then on_fail(); return end
local inv = meta:get_inventory(); local list_elements = ""; local warning_label = ""
if dispensable_amount < requested_amount then warning_label = "label[0,1;`"..S("Note: Amount was rounded down...").."`]" .. "style[label;color=yellow]" end
for i, item in ipairs(items_to_dispense) do inv:set_stack(item.name, 1, {name = item.name, count = item.count}); list_elements = list_elements .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";"..item.name..";"..(i-1)..",2;1,1;]" end
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal_output@"..customer_name, "size[8,4]" .. "label[0,0.5;"..S("Teller: Give these items to @1 (@2 MG):", customer_name, string.format("%.2f", dispensable_amount)).."]" .. warning_label .. list_elements .. "button_exit[2.5,3.25;3,1;take_all;"..S("Take All").."]")
else minetest.chat_send_player(player_name, S("[Bank] Insufficient funds or invalid amount.")); show_teller2_customer_view(player, pos, customer_name) end
else show_teller2_customer_view(player, pos, customer_name) end
elseif formname:find("bank_accounts:teller2_withdrawal_output@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal_output@(.*)")
if fields.take_all or fields.quit then
local inv = meta:get_inventory(); local any_left = false
for _, currency in ipairs(currency_values) do
local stack = inv:get_stack(currency.name, 1)
if not stack:is_empty() then
if fields.take_all then player:get_inventory():add_item("main", stack)
else local success = bank_accounts.add_balance(customer_name, get_item_value(stack) * stack:get_count(), "Teller Withdraw Cancel", S("Cancelled by teller @1", player_name), player_name); if success then any_left = true end end
inv:set_stack(currency.name, 1, "")
end
end
if any_left and fields.quit then minetest.chat_send_player(player_name, S("[Bank] Withdrawn money was returned to the customer's account.")) end
end
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Credit Payment Form
elseif formname:find("bank_accounts:teller2_pay_credit@") then
local customer_name = formname:match("bank_accounts:teller2_pay_credit@(.*)")
local amount_to_pay = 0; local is_valid = false
if fields.pay_rate then local credit_debt = bank_accounts.get_credit(customer_name); if credit_debt > 0 then amount_to_pay = math.max(1, math.floor(credit_debt * 0.04)) end; is_valid = true
elseif fields.pay_custom then
amount_to_pay = normalize_and_tonumber(fields.custom_amount); local min_payment = 0; local credit_debt = bank_accounts.get_credit(customer_name)
if credit_debt > 0 then min_payment = math.max(1, math.floor(credit_debt * 0.04)) end
if not amount_to_pay or amount_to_pay < min_payment then minetest.chat_send_player(player_name, S("[ATM] You must pay at least the minimum monthly rate."))
else is_valid = true end
end
if is_valid and amount_to_pay and amount_to_pay > 0 then
if bank_accounts.get_balance(customer_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[Bank] Insufficient funds."))
elseif bank_accounts.get_credit(customer_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[Bank] Player does not have that much credit debt."))
else
if not bank_accounts.add_balance(customer_name, -amount_to_pay, "Teller Credit Payment", S("Teller: @1", player_name), player_name) then on_fail(); return end
if not bank_accounts.add_credit(customer_name, -amount_to_pay, "Teller Credit Payment", S("Teller: @1", player_name), player_name) then on_fail(); return end
end
elseif fields.pay_custom and not is_valid then
elseif not fields.exit and not fields.quit and (fields.pay_rate or fields.pay_custom) then minetest.chat_send_player(player_name, S("[Bank] Invalid amount.")) end
show_teller2_customer_view(player, pos, customer_name)
-- Statement Form
elseif formname:find("bank_accounts:teller2_statement@") then
local customer_name = formname:match("bank_accounts:teller2_statement@(.*)")
if not customer_name then return end
if fields.view_balance then show_statement_form(player, pos, customer_name, "balance")
elseif fields.view_credit then show_statement_form(player, pos, customer_name, "credit")
elseif fields.back then show_teller2_customer_view(player, pos, customer_name) end
end
end)

290
computer2.lua.bak.1 Normal file
View file

@ -0,0 +1,290 @@
-- computer2.lua (Fixed translation error and deposit form flow)
-- Uses the global 'pos_info' variable for consistency.
-- This table will temporarily store which customer a teller is currently serving for the deposit form.
local teller_current_customer = {}
-- Copy currency definitions and helper functions from atm2.lua
local currency_values = {
{name = "currency:minegeld_100", value = 100},
{name = "currency:minegeld_50", value = 50},
{name = "currency:minegeld_10", value = 10},
{name = "currency:minegeld_5", value = 5},
{name = "currency:minegeld", value = 1},
{name = "currency:minegeld_cent_25", value = 0.25},
{name = "currency:minegeld_cent_10", value = 0.10},
{name = "currency:minegeld_cent_5", value = 0.05},
}
local function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
local function get_item_value(itemstack)
local name = itemstack:get_name(); for _, c in ipairs(currency_values) do if c.name == name then return c.value end end; return 0
end
local function amount_to_itemstacks(amount)
local items = {}; local rem = amount; for _, c in ipairs(currency_values) do if rem >= c.value then local count = math.floor(rem / c.value); if count > 0 then table.insert(items, {name=c.name, count=count}); rem = rem - (count*c.value); rem = round(rem, 2); if rem < 0.001 then break end end end end; return items
end
-- Formspec for the initial customer selection screen
local function show_teller2_selection_form(player, pos, search_term)
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, balance in pairs(all_accounts.balance) do
if not search_term or search_term == "" or name:lower():find(search_term:lower(), 1, true) then
table.insert(customer_list, minetest.formspec_escape(name))
end
end
table.sort(customer_list)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_select",
"size[8,9.5]"..
"label[0,0;"..S("Select Customer Account").."]"..
"label[0,0.5;"..S("Search by Name:").."]"..
"field[0,0.8;6,1;search_name;;"..minetest.formspec_escape(search_term or "").."]"..
"button_exit[6.2,0.8;1.8,1;search;"..S("Search").."]"..
"table[0,1.8;8,7;customer_list;"..table.concat(customer_list, ",").."]"
)
end
-- Formspec for viewing and managing a selected customer's account
local function show_teller2_customer_view(player, pos, customer_name)
local data = bank_accounts.get_account_data(customer_name)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_view@"..customer_name,
"size[8,9]"..
"label[0,0;"..S("Managing Account: @1", customer_name).."]"..
"label[0.5,1;"..S("Balance: @1", string.format("%.2f", data.balance).." MG").."]"..
"label[4.5,1;"..S("Credit Debt: @1", string.format("%.2f", data.credit).." MG").."]"..
"container[0,0.5;8,1.25]"..
"container_end[]"..
"button_exit[0.5,2.5;3,1;deposit;"..S("Deposit").."]"..
"button_exit[4.5,2.5;3,1;withdrawal;"..S("Withdrawal").."]"..
"button_exit[0.5,4;3,1;statement;"..S("Account Statement").."]"..
"button_exit[4.5,4;3,1;reset_pin;"..S("Reset PIN").."]"..
"button_exit[0.5,5.5;3,1;back_to_search;"..S("Back to Customer List").."]"..
"button_exit[4.5,5.5;3,1;wipe;"..S("Wipe Account").."]"
)
end
-- Formspec for the new deposit form
local function show_teller2_deposit_form(player, pos, customer_name)
local meta = minetest.get_meta(pos)
local deposited_amount = tonumber(meta:get_string("deposit_buffer") or "0")
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_deposit@"..customer_name,
"size[8,9]"..
"label[0,0.5;"..S("Deposit for @1", customer_name).."]"..
"label[0,1.5;"..S("Place coins and notes in the slot below.").."]"..
"list["..list_name..";deposit_slot;3.5,2.5;1,1;]"..
"label[0,4;"..S("Currently Deposited: @1 MG", string.format("%.2f", deposited_amount)).."]"..
"button_exit[0.5,5;3,1;confirm_deposit;"..S("Deposit").."]"..
"button_exit[4.5,5;3,1;return_deposit;"..S("Return").."]"..
"list[current_player;main;0,6;8,3;]")
end
-- Formspec for the account statement
local function show_statement_form(player, pos, customer_name, account_type)
local data = bank_accounts.get_account_data(customer_name)
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
-- KORREKTUR: 'purpose' wird direkt angezeigt, nicht erneut übersetzt.
local purpose = t.purpose or ""
local partner = t.other or ""
if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance).." MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit).." MG") end
local form_name = "bank_accounts:teller2_statement@" .. customer_name
local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1",customer_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player:get_player_name(), form_name, formspec)
end
minetest.register_node("bank_accounts:teller_computer2", {
description = S("Teller Computer Mark II"),
drawtype = "mesh",
mesh = "computer.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"computer2.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
on_construct = function(pos)
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
inv:set_size("deposit_slot", 1)
for _, currency in ipairs(currency_values) do inv:set_size(currency.name, 1) end
end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
if minetest.check_player_privs(player:get_player_name(), {bank_teller=true, server=true}) then
show_teller2_selection_form(player, pos, "")
else
minetest.chat_send_player(player:get_player_name(), S("[Bank] Insufficient privileges."))
end
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname ~= "deposit_slot" then return end
local value = get_item_value(stack)
if value > 0 then
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
local current_buffer = tonumber(meta:get_string("deposit_buffer") or "0")
meta:set_string("deposit_buffer", tostring(current_buffer + value * stack:get_count()))
inv:set_stack(listname, index, "")
local customer_name = teller_current_customer[player:get_player_name()]
if customer_name then
minetest.after(0, show_teller2_deposit_form, player, pos, customer_name)
end
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:teller2_") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local meta = minetest.get_meta(pos)
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
-- Customer Selection Form
if formname == "bank_accounts:teller2_select" then
local customer_name = fields.search_name or ""
if fields.customer_list then
local event = minetest.explode_table_event(fields.customer_list)
if event.type == "CHG" or event.type == "DCL" then
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, _ in pairs(all_accounts.balance) do
if not fields.search_name or fields.search_name == "" or name:lower():find(fields.search_name:lower(), 1, true) then
table.insert(customer_list, name)
end
end
table.sort(customer_list)
customer_name = customer_list[event.row]
end
end
if customer_name and bank_accounts.player_has_account(customer_name) then
show_teller2_customer_view(player, pos, customer_name)
else
show_teller2_selection_form(player, pos, fields.search_name)
end
-- Customer View Form
elseif formname:find("bank_accounts:teller2_view@") then
local customer_name = formname:match("bank_accounts:teller2_view@(.*)")
if not customer_name then return end
if fields.back_to_search then
show_teller2_selection_form(player, pos, "")
elseif fields.statement then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.reset_pin then
if not bank_accounts.set_pin(customer_name, "0000") then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Player's pin successfully reset!")) end
show_teller2_customer_view(player, pos, customer_name)
elseif fields.wipe then
if not bank_accounts.set_balance(customer_name, 0, "Teller Wipe", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Account successfully wiped!")) end
show_teller2_customer_view(player, pos, customer_name)
elseif fields.deposit then
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = customer_name
show_teller2_deposit_form(player, pos, customer_name)
elseif fields.withdrawal then
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal@"..customer_name, "size[8,8]" .. "field[2,4;5,1;money;"..S("Amount:")..";]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]")
end
-- Deposit Form
elseif formname:find("bank_accounts:teller2_deposit@") then
local customer_name = formname:match("bank_accounts:teller2_deposit@(.*)")
local buffer = tonumber(meta:get_string("deposit_buffer") or "0")
if fields.confirm_deposit then
if buffer > 0 then
if not bank_accounts.add_balance(customer_name, buffer, "Teller Deposit", S("Teller: @1", player_name), player_name) then
on_fail()
else
minetest.chat_send_player(player_name, S("[Bank] Deposited @1 for @2.", string.format("%.2f", buffer), customer_name))
end
end
elseif fields.return_deposit or fields.quit then
if buffer > 0 then
local items_to_return = amount_to_itemstacks(buffer)
for _, item in ipairs(items_to_return) do player:get_inventory():add_item("main", item) end
if fields.quit then minetest.chat_send_player(player_name, S("[Bank] The deposit was cancelled and returned to your inventory.")) end
end
end
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = nil
if not fields.quit then
show_teller2_customer_view(player, pos, customer_name)
end
-- Withdrawal Amount Entry
elseif formname:find("bank_accounts:teller2_withdrawal@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal@(.*)")
if fields.enter then
local requested_amount = normalize_and_tonumber(fields.money)
if requested_amount and requested_amount > 0 and bank_accounts.get_balance(customer_name) >= requested_amount then
local items_to_dispense = amount_to_itemstacks(requested_amount)
local dispensable_amount = 0
for _, item_data in ipairs(items_to_dispense) do for _, currency in ipairs(currency_values) do if currency.name == item_data.name then dispensable_amount = dispensable_amount + (currency.value * item_data.count); break end end end
if not bank_accounts.add_balance(customer_name, -dispensable_amount, "Teller Withdrawal", S("Teller: @1", player_name), player_name) then on_fail(); return end
local inv = meta:get_inventory(); local list_elements = ""; local warning_label = ""
if dispensable_amount < requested_amount then warning_label = "label[0,1;`"..S("Note: Amount was rounded down...").."`]" .. "style[label;color=yellow]" end
for i, item in ipairs(items_to_dispense) do
inv:set_stack(item.name, 1, {name = item.name, count = item.count})
list_elements = list_elements .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";"..item.name..";"..(i-1)..",2;1,1;]"
end
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal_output@"..customer_name, "size[8,4]" .. "label[0,0.5;"..S("Teller: Give these items to @1 (@2 MG):", customer_name, string.format("%.2f", dispensable_amount)).."]" .. warning_label .. list_elements .. "button_exit[2.5,3.25;3,1;take_all;"..S("Take All").."]")
else minetest.chat_send_player(player_name, S("[Bank] Insufficient funds or invalid amount.")); show_teller2_customer_view(player, pos, customer_name) end
else show_teller2_customer_view(player, pos, customer_name) end
-- Withdrawal Output
elseif formname:find("bank_accounts:teller2_withdrawal_output@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal_output@(.*)")
if fields.take_all or fields.quit then
local inv = meta:get_inventory(); local any_left = false
for _, currency in ipairs(currency_values) do
local stack = inv:get_stack(currency.name, 1)
if not stack:is_empty() then
if fields.take_all then player:get_inventory():add_item("main", stack)
else
local success = bank_accounts.add_balance(customer_name, get_item_value(stack) * stack:get_count(), "Teller Withdraw Cancel", S("Cancelled by teller @1", player_name), player_name)
if success then any_left = true end
end
inv:set_stack(currency.name, 1, "")
end
end
if any_left and fields.quit then minetest.chat_send_player(player_name, S("[Bank] Withdrawn money was returned to the customer's account.")) end
end
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Statement Form
elseif formname:find("bank_accounts:teller2_statement@") then
local customer_name = formname:match("bank_accounts:teller2_statement@(.*)")
if not customer_name then return end
if fields.view_balance then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.view_credit then
show_statement_form(player, pos, customer_name, "credit")
elseif fields.back then
show_teller2_customer_view(player, pos, customer_name)
end
end
end)

304
computer2.lua.bak.2 Normal file
View file

@ -0,0 +1,304 @@
-- computer2.lua (Added Credit Payment and Safety Confirmations)
-- Uses the global 'pos_info' variable for consistency.
-- This table will temporarily store which customer a teller is currently serving for the deposit form.
local teller_current_customer = {}
-- Copy currency definitions and helper functions from atm2.lua
local currency_values = {
{name = "currency:minegeld_100", value = 100},
{name = "currency:minegeld_50", value = 50},
{name = "currency:minegeld_10", value = 10},
{name = "currency:minegeld_5", value = 5},
{name = "currency:minegeld", value = 1},
{name = "currency:minegeld_cent_25", value = 0.25},
{name = "currency:minegeld_cent_10", value = 0.10},
{name = "currency:minegeld_cent_5", value = 0.05},
}
local function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
local function get_item_value(itemstack)
local name = itemstack:get_name(); for _, c in ipairs(currency_values) do if c.name == name then return c.value end end; return 0
end
local function amount_to_itemstacks(amount)
local items = {}; local rem = amount; for _, c in ipairs(currency_values) do if rem >= c.value then local count = math.floor(rem / c.value); if count > 0 then table.insert(items, {name=c.name, count=count}); rem = rem - (count*c.value); rem = round(rem, 2); if rem < 0.001 then break end end end end; return items
end
-- Formspec for the initial customer selection screen
local function show_teller2_selection_form(player, pos, search_term)
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, balance in pairs(all_accounts.balance) do
if not search_term or search_term == "" or name:lower():find(search_term:lower(), 1, true) then
table.insert(customer_list, minetest.formspec_escape(name))
end
end
table.sort(customer_list)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_select",
"size[8,9.5]"..
"label[0,0;"..S("Select Customer Account").."]"..
"label[0.5,0.6;"..S("Search by Name:").."]"..
"field[0.5,1;7,1;search_name;;"..minetest.formspec_escape(search_term or "").."]"..
"button_exit[6.2,0.5;1.8,1;search;"..S("Search").."]"..
"table[0,1.8;8,7;customer_list;"..table.concat(customer_list, ",").."]"
)
end
-- Formspec for viewing and managing a selected customer's account
local function show_teller2_customer_view(player, pos, customer_name)
local data = bank_accounts.get_account_data(customer_name)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_view@"..customer_name,
"size[8,9]"..
"label[0,0;"..S("Managing Account: @1", customer_name).."]"..
"label[0.5,1;"..S("Balance: @1", string.format("%.2f", data.balance).." MG").."]"..
"label[4.5,1;"..S("Credit Debt: @1", string.format("%.2f", data.credit).." MG").."]"..
"container[0,0.5;8,1.25]"..
"container_end[]"..
"button_exit[0.5,2.5;3,1;deposit;"..S("Deposit").."]"..
"button_exit[4.5,2.5;3,1;withdrawal;"..S("Withdrawal").."]"..
"button_exit[0.5,4;3,1;pay_credit;"..S("Pay Credit Debt").."]"..
"button_exit[4.5,4;3,1;statement;"..S("Account Statement").."]"..
"button_exit[0.5,5.5;3,1;reset_pin;"..S("Reset PIN").."]"..
"button_exit[4.5,5.5;3,1;wipe;"..S("Wipe Account").."]"..
"button_exit[0.5,7;3,1;back_to_search;"..S("Back to Customer List").."]"
)
end
-- Formspec for the new deposit form
local function show_teller2_deposit_form(player, pos, customer_name)
local meta = minetest.get_meta(pos)
local deposited_amount = tonumber(meta:get_string("deposit_buffer") or "0")
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_deposit@"..customer_name,
"size[8,9]"..
"label[0,0.5;"..S("Deposit for @1", customer_name).."]"..
"label[0,1.5;"..S("Place coins and notes in the slot below.").."]"..
"list["..list_name..";deposit_slot;3.5,2.5;1,1;]"..
"label[0,4;"..S("Currently Deposited: @1 MG", string.format("%.2f", deposited_amount)).."]"..
"button_exit[0.5,5;3,1;confirm_deposit;"..S("Deposit").."]"..
"button_exit[4.5,5;3,1;return_deposit;"..S("Return").."]"..
"list[current_player;main;0,6;8,3;]")
end
-- Formspec for the account statement (re-used from other files)
local function show_statement_form(player, pos, customer_name, account_type)
local data = bank_accounts.get_account_data(customer_name)
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = S(t.purpose or ""); local partner = t.other or ""; if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance).." MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit).." MG") end
local form_name = "bank_accounts:teller2_statement@" .. customer_name
local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1",customer_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player:get_player_name(), form_name, formspec)
end
minetest.register_node("bank_accounts:teller_computer2", {
description = S("Teller Computer Mark II"),
drawtype = "mesh",
mesh = "computer.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"computer2.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
on_construct = function(pos)
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
inv:set_size("deposit_slot", 1)
for _, currency in ipairs(currency_values) do inv:set_size(currency.name, 1) end
end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
if minetest.check_player_privs(player:get_player_name(), {bank_teller=true, server=true}) then
show_teller2_selection_form(player, pos, "")
else
minetest.chat_send_player(player:get_player_name(), S("[Bank] Insufficient privileges."))
end
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname ~= "deposit_slot" then return end
local value = get_item_value(stack)
if value > 0 then
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
local current_buffer = tonumber(meta:get_string("deposit_buffer") or "0")
meta:set_string("deposit_buffer", tostring(current_buffer + value * stack:get_count()))
inv:set_stack(listname, index, "")
local customer_name = teller_current_customer[player:get_player_name()]
if customer_name then
minetest.after(0, show_teller2_deposit_form, player, pos, customer_name)
end
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:teller2_") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local meta = minetest.get_meta(pos)
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
-- Customer Selection Form
if formname == "bank_accounts:teller2_select" then
local customer_name = fields.search_name or ""
if fields.customer_list then
local event = minetest.explode_table_event(fields.customer_list)
if event.type == "CHG" or event.type == "DCL" then
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, _ in pairs(all_accounts.balance) do
if not fields.search_name or fields.search_name == "" or name:lower():find(fields.search_name:lower(), 1, true) then
table.insert(customer_list, name)
end
end
table.sort(customer_list)
customer_name = customer_list[event.row]
end
end
if customer_name and bank_accounts.player_has_account(customer_name) then
show_teller2_customer_view(player, pos, customer_name)
else
show_teller2_selection_form(player, pos, fields.search_name)
end
-- Customer View Form
elseif formname:find("bank_accounts:teller2_view@") then
local customer_name = formname:match("bank_accounts:teller2_view@(.*)")
if not customer_name then return end
if fields.back_to_search then
show_teller2_selection_form(player, pos, "")
elseif fields.statement then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.reset_pin then
minetest.show_formspec(player_name, "bank_accounts:teller2_reset_pin_confirm@"..customer_name, "size[8,4]".."label[1,1;"..S("Really reset PIN for @1?", customer_name).."]".."button_exit[1,3;2.5,1;yes_reset;"..S("Yes").."]".."button_exit[4.5,3;2.5,1;no;"..S("No").."]")
elseif fields.wipe then
minetest.show_formspec(player_name, "bank_accounts:teller2_wipe_confirm@"..customer_name, "size[8,4]".."label[1,1;"..S("Really wipe account for @1?", customer_name).."]".."button_exit[1,3;2.5,1;yes_wipe;"..S("Yes").."]".."button_exit[4.5,3;2.5,1;no;"..S("No").."]")
elseif fields.deposit then
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = customer_name
show_teller2_deposit_form(player, pos, customer_name)
elseif fields.withdrawal then
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal@"..customer_name, "size[8,8]" .. "field[2,4;5,1;money;"..S("Amount:")..";]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]")
elseif fields.pay_credit then
local credit_debt = bank_accounts.get_credit(customer_name)
local next_rate = 0
if credit_debt > 0 then next_rate = math.max(1, math.floor(credit_debt * 0.04)) end
minetest.show_formspec(player_name, "bank_accounts:teller2_pay_credit@"..customer_name, "size[8,8]" .. "label[0,0.5;"..S("Total Credit Debt: @1", string.format("%.2f", credit_debt).." MG").."]".. "label[0,2;"..S("Next calculated rate:").."]".. "button_exit[0,2.5;3.5,1;pay_rate;"..S("Pay Next Rate (@1 MG)", next_rate).."]" .. "label[4.2,2;"..S("Or enter a custom amount:").."]" .. "field[4.5,2.8;3.5,1;custom_amount;"..""..";]" .. "button_exit[4.2,3.5;3.5,1;pay_custom;"..S("Pay Custom Amount").."]" .. "button_exit[3,7;2,1;exit;"..S("Cancel").."]")
end
-- Confirmation Forms
elseif formname:find("bank_accounts:teller2_reset_pin_confirm@") then
local customer_name = formname:match("bank_accounts:teller2_reset_pin_confirm@(.*)")
if fields.yes_reset then
if not bank_accounts.set_pin(customer_name, "0000") then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Player's pin successfully reset!")) end
end
show_teller2_customer_view(player, pos, customer_name)
elseif formname:find("bank_accounts:teller2_wipe_confirm@") then
local customer_name = formname:match("bank_accounts:teller2_wipe_confirm@(.*)")
if fields.yes_wipe then
if not bank_accounts.set_balance(customer_name, 0, "Teller Wipe", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Account successfully wiped!")) end
end
show_teller2_customer_view(player, pos, customer_name)
-- Deposit Form
elseif formname:find("bank_accounts:teller2_deposit@") then
local customer_name = formname:match("bank_accounts:teller2_deposit@(.*)")
local buffer = tonumber(meta:get_string("deposit_buffer") or "0")
if fields.confirm_deposit then
if buffer > 0 then
if not bank_accounts.add_balance(customer_name, buffer, "Teller Deposit", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Deposited @1 for @2.", string.format("%.2f", buffer), customer_name)) end
end
elseif fields.return_deposit or fields.quit then
if buffer > 0 then
local items_to_return = amount_to_itemstacks(buffer)
for _, item in ipairs(items_to_return) do player:get_inventory():add_item("main", item) end
if fields.quit then minetest.chat_send_player(player_name, S("[Bank] The deposit was cancelled and returned to your inventory.")) end
end
end
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = nil
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Withdrawal Forms
elseif formname:find("bank_accounts:teller2_withdrawal@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal@(.*)")
if fields.enter then
local requested_amount = normalize_and_tonumber(fields.money)
if requested_amount and requested_amount > 0 and bank_accounts.get_balance(customer_name) >= requested_amount then
local items_to_dispense = amount_to_itemstacks(requested_amount)
local dispensable_amount = 0
for _, item_data in ipairs(items_to_dispense) do for _, currency in ipairs(currency_values) do if currency.name == item_data.name then dispensable_amount = dispensable_amount + (currency.value * item_data.count); break end end end
if not bank_accounts.add_balance(customer_name, -dispensable_amount, "Teller Withdrawal", S("Teller: @1", player_name), player_name) then on_fail(); return end
local inv = meta:get_inventory(); local list_elements = ""; local warning_label = ""
if dispensable_amount < requested_amount then warning_label = "label[0,1;`"..S("Note: Amount was rounded down...").."`]" .. "style[label;color=yellow]" end
for i, item in ipairs(items_to_dispense) do inv:set_stack(item.name, 1, {name = item.name, count = item.count}); list_elements = list_elements .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";"..item.name..";"..(i-1)..",2;1,1;]" end
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal_output@"..customer_name, "size[8,4]" .. "label[0,0.5;"..S("Teller: Give these items to @1 (@2 MG):", customer_name, string.format("%.2f", dispensable_amount)).."]" .. warning_label .. list_elements .. "button_exit[2.5,3.25;3,1;take_all;"..S("Take All").."]")
else minetest.chat_send_player(player_name, S("[Bank] Insufficient funds or invalid amount.")); show_teller2_customer_view(player, pos, customer_name) end
else show_teller2_customer_view(player, pos, customer_name) end
elseif formname:find("bank_accounts:teller2_withdrawal_output@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal_output@(.*)")
if fields.take_all or fields.quit then
local inv = meta:get_inventory(); local any_left = false
for _, currency in ipairs(currency_values) do
local stack = inv:get_stack(currency.name, 1)
if not stack:is_empty() then
if fields.take_all then player:get_inventory():add_item("main", stack)
else local success = bank_accounts.add_balance(customer_name, get_item_value(stack) * stack:get_count(), "Teller Withdraw Cancel", S("Cancelled by teller @1", player_name), player_name); if success then any_left = true end end
inv:set_stack(currency.name, 1, "")
end
end
if any_left and fields.quit then minetest.chat_send_player(player_name, S("[Bank] Withdrawn money was returned to the customer's account.")) end
end
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Credit Payment Form
elseif formname:find("bank_accounts:teller2_pay_credit@") then
local customer_name = formname:match("bank_accounts:teller2_pay_credit@(.*)")
local amount_to_pay = 0; local is_valid = false
if fields.pay_rate then local credit_debt = bank_accounts.get_credit(customer_name); if credit_debt > 0 then amount_to_pay = math.max(1, math.floor(credit_debt * 0.04)) end; is_valid = true
elseif fields.pay_custom then
amount_to_pay = normalize_and_tonumber(fields.custom_amount); local min_payment = 0; local credit_debt = bank_accounts.get_credit(customer_name)
if credit_debt > 0 then min_payment = math.max(1, math.floor(credit_debt * 0.04)) end
if not amount_to_pay or amount_to_pay < min_payment then minetest.chat_send_player(player_name, S("[ATM] You must pay at least the minimum monthly rate."))
else is_valid = true end
end
if is_valid and amount_to_pay and amount_to_pay > 0 then
if bank_accounts.get_balance(customer_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[Bank] Insufficient funds."))
elseif bank_accounts.get_credit(customer_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[Bank] Player does not have that much credit debt."))
else
if not bank_accounts.add_balance(customer_name, -amount_to_pay, "Teller Credit Payment", S("Teller: @1", player_name), player_name) then on_fail(); return end
if not bank_accounts.add_credit(customer_name, -amount_to_pay, "Teller Credit Payment", S("Teller: @1", player_name), player_name) then on_fail(); return end
end
elseif fields.pay_custom and not is_valid then
elseif not fields.exit and not fields.quit and (fields.pay_rate or fields.pay_custom) then minetest.chat_send_player(player_name, S("[Bank] Invalid amount.")) end
show_teller2_customer_view(player, pos, customer_name)
-- Statement Form
elseif formname:find("bank_accounts:teller2_statement@") then
local customer_name = formname:match("bank_accounts:teller2_statement@(.*)")
if not customer_name then return end
if fields.view_balance then show_statement_form(player, pos, customer_name, "balance")
elseif fields.view_credit then show_statement_form(player, pos, customer_name, "credit")
elseif fields.back then show_teller2_customer_view(player, pos, customer_name) end
end
end)

311
computer2.lua.bak.3 Normal file
View file

@ -0,0 +1,311 @@
-- computer2.lua (Fixed translation error and search layout)
-- Uses the global 'pos_info' variable for consistency.
-- This table will temporarily store which customer a teller is currently serving for the deposit form.
local teller_current_customer = {}
-- Copy currency definitions and helper functions from atm2.lua
local currency_values = {
{name = "currency:minegeld_100", value = 100},
{name = "currency:minegeld_50", value = 50},
{name = "currency:minegeld_10", value = 10},
{name = "currency:minegeld_5", value = 5},
{name = "currency:minegeld", value = 1},
{name = "currency:minegeld_cent_25", value = 0.25},
{name = "currency:minegeld_cent_10", value = 0.10},
{name = "currency:minegeld_cent_5", value = 0.05},
}
local function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
local function get_item_value(itemstack)
local name = itemstack:get_name(); for _, c in ipairs(currency_values) do if c.name == name then return c.value end end; return 0
end
local function amount_to_itemstacks(amount)
local items = {}; local rem = amount; for _, c in ipairs(currency_values) do if rem >= c.value then local count = math.floor(rem / c.value); if count > 0 then table.insert(items, {name=c.name, count=count}); rem = rem - (count*c.value); rem = round(rem, 2); if rem < 0.001 then break end end end end; return items
end
-- Formspec for the initial customer selection screen
local function show_teller2_selection_form(player, pos, search_term)
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, balance in pairs(all_accounts.balance) do
if not search_term or search_term == "" or name:lower():find(search_term:lower(), 1, true) then
table.insert(customer_list, minetest.formspec_escape(name))
end
end
table.sort(customer_list)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_select",
"size[8,9.5]"..
"label[0,0;"..S("Select Customer Account").."]"..
-- KORRIGIERTES LAYOUT: Label entfernt, Feld neu positioniert
"field[0.29,0.85;5.8,1;search_name;;"..minetest.formspec_escape(search_term or "").."]"..
"button_exit[6.2,0.55;1.8,1;search;"..S("Search").."]"..
"table[0,1.8;8,7;customer_list;"..table.concat(customer_list, ",").."]"
)
end
-- Formspec for viewing and managing a selected customer's account
local function show_teller2_customer_view(player, pos, customer_name)
local data = bank_accounts.get_account_data(customer_name)
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_view@"..customer_name,
"size[8,9]"..
"label[0,0;"..S("Managing Account: @1", customer_name).."]"..
"label[0.5,1;"..S("Balance: @1", string.format("%.2f", data.balance).." MG").."]"..
"label[4.5,1;"..S("Credit Debt: @1", string.format("%.2f", data.credit).." MG").."]"..
"container[0,0.5;8,1.25]"..
"container_end[]"..
"button_exit[0.5,2.5;3,1;deposit;"..S("Deposit").."]"..
"button_exit[4.5,2.5;3,1;withdrawal;"..S("Withdrawal").."]"..
"button_exit[0.5,4;3,1;pay_credit;"..S("Pay Credit Debt").."]"..
"button_exit[4.5,4;3,1;statement;"..S("Account Statement").."]"..
"button_exit[0.5,5.5;3,1;reset_pin;"..S("Reset PIN").."]"..
"button_exit[4.5,5.5;3,1;wipe;"..S("Wipe Account").."]"..
"button_exit[0.5,7;3,1;back_to_search;"..S("Back to Customer List").."]"
)
end
-- Formspec for the new deposit form
local function show_teller2_deposit_form(player, pos, customer_name)
local meta = minetest.get_meta(pos)
local deposited_amount = tonumber(meta:get_string("deposit_buffer") or "0")
local list_name = "nodemeta:" .. pos.x .. "," .. pos.y .. "," .. pos.z
minetest.show_formspec(player:get_player_name(), "bank_accounts:teller2_deposit@"..customer_name,
"size[8,9]"..
"label[0,0.5;"..S("Deposit for @1", customer_name).."]"..
"label[0,1.5;"..S("Place coins and notes in the slot below.").."]"..
"list["..list_name..";deposit_slot;3.5,2.5;1,1;]"..
"label[0,4;"..S("Currently Deposited: @1 MG", string.format("%.2f", deposited_amount)).."]"..
"button_exit[0.5,5;3,1;confirm_deposit;"..S("Deposit").."]"..
"button_exit[4.5,5;3,1;return_deposit;"..S("Return").."]"..
"list[current_player;main;0,6;8,3;]")
end
-- Formspec for the account statement
local function show_statement_form(player, pos, customer_name, account_type)
local data = bank_accounts.get_account_data(customer_name)
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
-- KORREKTUR: 'purpose' wird direkt angezeigt, nicht erneut übersetzt.
local purpose = t.purpose or ""
local partner = t.other or ""
if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance).." MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit).." MG") end
local form_name = "bank_accounts:teller2_statement@" .. customer_name
local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1",customer_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player:get_player_name(), form_name, formspec)
end
minetest.register_node("bank_accounts:teller_computer2", {
description = S("Teller Computer Mark II"),
drawtype = "mesh",
mesh = "computer.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"computer2.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
on_construct = function(pos)
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
inv:set_size("deposit_slot", 1)
for _, currency in ipairs(currency_values) do inv:set_size(currency.name, 1) end
end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
if minetest.check_player_privs(player:get_player_name(), {bank_teller=true, server=true}) then
show_teller2_selection_form(player, pos, "")
else
minetest.chat_send_player(player:get_player_name(), S("[Bank] Insufficient privileges."))
end
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname ~= "deposit_slot" then return end
local value = get_item_value(stack)
if value > 0 then
local meta = minetest.get_meta(pos); local inv = meta:get_inventory()
local current_buffer = tonumber(meta:get_string("deposit_buffer") or "0")
meta:set_string("deposit_buffer", tostring(current_buffer + value * stack:get_count()))
inv:set_stack(listname, index, "")
local customer_name = teller_current_customer[player:get_player_name()]
if customer_name then
minetest.after(0, show_teller2_deposit_form, player, pos, customer_name)
end
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:teller2_") then return end
local player_name = player:get_player_name(); local pos = pos_info; if not pos then return end
local meta = minetest.get_meta(pos)
local function on_fail()
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
end
-- Customer Selection Form
if formname == "bank_accounts:teller2_select" then
local customer_name = fields.search_name or ""
if fields.customer_list then
local event = minetest.explode_table_event(fields.customer_list)
if event.type == "CHG" or event.type == "DCL" then
local all_accounts = bank_accounts.get_all_data()
local customer_list = {}
for name, _ in pairs(all_accounts.balance) do
if not fields.search_name or fields.search_name == "" or name:lower():find(fields.search_name:lower(), 1, true) then
table.insert(customer_list, name)
end
end
table.sort(customer_list)
customer_name = customer_list[event.row]
end
end
if customer_name and bank_accounts.player_has_account(customer_name) then
show_teller2_customer_view(player, pos, customer_name)
else
show_teller2_selection_form(player, pos, fields.search_name)
end
-- Customer View Form
elseif formname:find("bank_accounts:teller2_view@") then
local customer_name = formname:match("bank_accounts:teller2_view@(.*)")
if not customer_name then return end
if fields.back_to_search then
show_teller2_selection_form(player, pos, "")
elseif fields.statement then
show_statement_form(player, pos, customer_name, "balance")
elseif fields.reset_pin then
minetest.show_formspec(player_name, "bank_accounts:teller2_reset_pin_confirm@"..customer_name, "size[8,4]".."label[1,1;"..S("Really reset PIN for @1?", customer_name).."]".."button_exit[1,3;2.5,1;yes_reset;"..S("Yes").."]".."button_exit[4.5,3;2.5,1;no;"..S("No").."]")
elseif fields.wipe then
minetest.show_formspec(player_name, "bank_accounts:teller2_wipe_confirm@"..customer_name, "size[8,4]".."label[1,1;"..S("Really wipe account for @1?", customer_name).."]".."button_exit[1,3;2.5,1;yes_wipe;"..S("Yes").."]".."button_exit[4.5,3;2.5,1;no;"..S("No").."]")
elseif fields.deposit then
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = customer_name
show_teller2_deposit_form(player, pos, customer_name)
elseif fields.withdrawal then
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal@"..customer_name, "size[8,8]" .. "field[2,4;5,1;money;"..S("Amount:")..";]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]" .. "button_exit[5,6;2,1;enter;"..S("Enter").."]")
elseif fields.pay_credit then
local credit_debt = bank_accounts.get_credit(customer_name)
local next_rate = 0
if credit_debt > 0 then next_rate = math.max(1, math.floor(credit_debt * 0.04)) end
minetest.show_formspec(player_name, "bank_accounts:teller2_pay_credit@"..customer_name, "size[8,8]" .. "label[0,0.5;"..S("Total Credit Debt: @1", string.format("%.2f", credit_debt).." MG").."]".. "label[0,2;"..S("Next calculated rate:").."]".. "button_exit[0,2.5;3.5,1;pay_rate;"..S("Pay Next Rate (@1 MG)", next_rate).."]" .. "label[4.2,2;"..S("Or enter a custom amount:").."]" .. "field[4.5,2.8;3.5,1;custom_amount;"..""..";]" .. "button_exit[4.2,3.5;3.5,1;pay_custom;"..S("Pay Custom Amount").."]" .. "button_exit[3,7;2,1;exit;"..S("Cancel").."]")
end
-- Confirmation Forms
elseif formname:find("bank_accounts:teller2_reset_pin_confirm@") then
local customer_name = formname:match("bank_accounts:teller2_reset_pin_confirm@(.*)")
if fields.yes_reset then
if not bank_accounts.set_pin(customer_name, "0000") then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Player's pin successfully reset!")) end
end
show_teller2_customer_view(player, pos, customer_name)
elseif formname:find("bank_accounts:teller2_wipe_confirm@") then
local customer_name = formname:match("bank_accounts:teller2_wipe_confirm@(.*)")
if fields.yes_wipe then
if not bank_accounts.set_balance(customer_name, 0, "Teller Wipe", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Account successfully wiped!")) end
end
show_teller2_customer_view(player, pos, customer_name)
-- Deposit Form
elseif formname:find("bank_accounts:teller2_deposit@") then
local customer_name = formname:match("bank_accounts:teller2_deposit@(.*)")
local buffer = tonumber(meta:get_string("deposit_buffer") or "0")
if fields.confirm_deposit then
if buffer > 0 then
if not bank_accounts.add_balance(customer_name, buffer, "Teller Deposit", S("Teller: @1", player_name), player_name) then on_fail() else minetest.chat_send_player(player_name, S("[Bank] Deposited @1 for @2.", string.format("%.2f", buffer), customer_name)) end
end
elseif fields.return_deposit or fields.quit then
if buffer > 0 then
local items_to_return = amount_to_itemstacks(buffer)
for _, item in ipairs(items_to_return) do player:get_inventory():add_item("main", item) end
if fields.quit then minetest.chat_send_player(player_name, S("[Bank] The deposit was cancelled and returned to your inventory.")) end
end
end
meta:set_string("deposit_buffer", "0")
teller_current_customer[player_name] = nil
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Withdrawal Forms
elseif formname:find("bank_accounts:teller2_withdrawal@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal@(.*)")
if fields.enter then
local requested_amount = normalize_and_tonumber(fields.money)
if requested_amount and requested_amount > 0 and bank_accounts.get_balance(customer_name) >= requested_amount then
local items_to_dispense = amount_to_itemstacks(requested_amount)
local dispensable_amount = 0
for _, item_data in ipairs(items_to_dispense) do for _, currency in ipairs(currency_values) do if currency.name == item_data.name then dispensable_amount = dispensable_amount + (currency.value * item_data.count); break end end end
if not bank_accounts.add_balance(customer_name, -dispensable_amount, "Teller Withdrawal", S("Teller: @1", player_name), player_name) then on_fail(); return end
local inv = meta:get_inventory(); local list_elements = ""; local warning_label = ""
if dispensable_amount < requested_amount then warning_label = "label[0,1;`"..S("Note: Amount was rounded down...").."`]" .. "style[label;color=yellow]" end
for i, item in ipairs(items_to_dispense) do inv:set_stack(item.name, 1, {name = item.name, count = item.count}); list_elements = list_elements .. "list[nodemeta:"..pos.x..","..pos.y..","..pos.z..";"..item.name..";"..(i-1)..",2;1,1;]" end
minetest.show_formspec(player_name, "bank_accounts:teller2_withdrawal_output@"..customer_name, "size[8,4]" .. "label[0,0.5;"..S("Teller: Give these items to @1 (@2 MG):", customer_name, string.format("%.2f", dispensable_amount)).."]" .. warning_label .. list_elements .. "button_exit[2.5,3.25;3,1;take_all;"..S("Take All").."]")
else minetest.chat_send_player(player_name, S("[Bank] Insufficient funds or invalid amount.")); show_teller2_customer_view(player, pos, customer_name) end
else show_teller2_customer_view(player, pos, customer_name) end
elseif formname:find("bank_accounts:teller2_withdrawal_output@") then
local customer_name = formname:match("bank_accounts:teller2_withdrawal_output@(.*)")
if fields.take_all or fields.quit then
local inv = meta:get_inventory(); local any_left = false
for _, currency in ipairs(currency_values) do
local stack = inv:get_stack(currency.name, 1)
if not stack:is_empty() then
if fields.take_all then player:get_inventory():add_item("main", stack)
else local success = bank_accounts.add_balance(customer_name, get_item_value(stack) * stack:get_count(), "Teller Withdraw Cancel", S("Cancelled by teller @1", player_name), player_name); if success then any_left = true end end
inv:set_stack(currency.name, 1, "")
end
end
if any_left and fields.quit then minetest.chat_send_player(player_name, S("[Bank] Withdrawn money was returned to the customer's account.")) end
end
if not fields.quit then show_teller2_customer_view(player, pos, customer_name) end
-- Credit Payment Form
elseif formname:find("bank_accounts:teller2_pay_credit@") then
local customer_name = formname:match("bank_accounts:teller2_pay_credit@(.*)")
local amount_to_pay = 0; local is_valid = false
if fields.pay_rate then local credit_debt = bank_accounts.get_credit(customer_name); if credit_debt > 0 then amount_to_pay = math.max(1, math.floor(credit_debt * 0.04)) end; is_valid = true
elseif fields.pay_custom then
amount_to_pay = normalize_and_tonumber(fields.custom_amount); local min_payment = 0; local credit_debt = bank_accounts.get_credit(customer_name)
if credit_debt > 0 then min_payment = math.max(1, math.floor(credit_debt * 0.04)) end
if not amount_to_pay or amount_to_pay < min_payment then minetest.chat_send_player(player_name, S("[ATM] You must pay at least the minimum monthly rate."))
else is_valid = true end
end
if is_valid and amount_to_pay and amount_to_pay > 0 then
if bank_accounts.get_balance(customer_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[Bank] Insufficient funds."))
elseif bank_accounts.get_credit(customer_name) < amount_to_pay then minetest.chat_send_player(player_name, S("[Bank] Player does not have that much credit debt."))
else
if not bank_accounts.add_balance(customer_name, -amount_to_pay, "Teller Credit Payment", S("Teller: @1", player_name), player_name) then on_fail(); return end
if not bank_accounts.add_credit(customer_name, -amount_to_pay, "Teller Credit Payment", S("Teller: @1", player_name), player_name) then on_fail(); return end
end
elseif fields.pay_custom and not is_valid then
elseif not fields.exit and not fields.quit and (fields.pay_rate or fields.pay_custom) then minetest.chat_send_player(player_name, S("[Bank] Invalid amount.")) end
show_teller2_customer_view(player, pos, customer_name)
-- Statement Form
elseif formname:find("bank_accounts:teller2_statement@") then
local customer_name = formname:match("bank_accounts:teller2_statement@(.*)")
if not customer_name then return end
if fields.view_balance then show_statement_form(player, pos, customer_name, "balance")
elseif fields.view_credit then show_statement_form(player, pos, customer_name, "credit")
elseif fields.back then show_teller2_customer_view(player, pos, customer_name) end
end
end)

View file

@ -1,45 +1,29 @@
--[[
Core API and Data Handling
--------------------------
This file manages all low-level reading from and writing to the 'accounts' data file.
It provides a safe and centralized API for all other mod files to interact with player data.
--]]
-- functions.lua (Corrected for Deadlock issue during interest calculation)
local world_path = minetest.get_worldpath()
local file_path = world_path .. "/accounts"
-- Normalizes a string to use '.' as a decimal separator and converts to a number.
-- Handles both "1,23" and "1.23".
function normalize_and_tonumber(str)
if not str then
return nil
end
if not str then return nil end
str = str:gsub(",", ".")
return tonumber(str)
end
-- Internal function to read the entire accounts database file.
-- Returns a populated table or a clean default structure on any error.
local function read_accounts_file()
local f = io.open(file_path, "r")
if not f then
return { balance = {}, pin = {}, credit = {}, history = {} }
end
if not f then return { balance = {}, pin = {}, credit = {}, history = {} } end
local data = f:read("*a")
f:close()
if data == "" or data == nil then
return { balance = {}, pin = {}, credit = {}, history = {} }
end
if data == "" or data == nil then return { balance = {}, pin = {}, credit = {}, history = {} } end
return minetest.deserialize(data) or { balance = {}, pin = {}, credit = {}, history = {} }
end
-- Internal function to write the entire accounts database file.
-- Includes a locking mechanism to prevent race conditions with the interest script.
local function save_accounts_file(data)
-- If the daily interest calculation is running, wait in short intervals until it's done.
while bank_accounts and bank_accounts.is_calculating_interest do
minetest.log("action", "[bank_accounts] Interest calculation in progress, delaying save operation...")
minetest.sleep(0.5)
-- CORRECTED: The save function now accepts an optional parameter to bypass the lock.
local function save_accounts_file(data, bypass_lock)
-- If the interest script is running AND the lock bypass is NOT active, block the save.
if not bypass_lock and bank_accounts and bank_accounts.is_calculating_interest then
minetest.log("warning", "[bank_accounts] Save operation blocked: Daily interest calculation is in progress.")
return false -- Signal failure
end
local f, err = io.open(file_path, "w")
@ -52,51 +36,34 @@ local function save_accounts_file(data)
return true
end
-- Internal function to log a single transaction to a player's history.
local function log_transaction(data, player_name, account_type, amount, new_total, trans_type, purpose, other_party)
-- Ensure history tables exist.
if not data.history then data.history = {} end
if not data.history[player_name] then data.history[player_name] = {} end
local transaction = {
timestamp = os.time(),
type = trans_type or "unknown",
account = account_type, -- "balance" or "credit"
amount = amount,
new_total = new_total,
purpose = purpose or "", -- e.g., "Teller: Rage87" or user-defined text
other = other_party or "" -- e.g., recipient or sender name
}
-- Insert at the beginning of the list to show newest transactions first.
local transaction = { timestamp = os.time(), type = trans_type or "unknown", account = account_type, amount = amount, new_total = new_total, purpose = purpose or "", other = other_party or "" }
table.insert(data.history[player_name], 1, transaction)
-- Limit history to the last 200 entries to prevent the file from growing indefinitely.
while #data.history[player_name] > 200 do
table.remove(data.history[player_name])
end
end
-- On server start, create the accounts file if it doesn't exist.
-- On first run, bypass lock to create file safely.
do
local f = io.open(file_path, "r")
if not f then
save_accounts_file({ balance = {}, pin = {}, credit = {}, history = {} })
save_accounts_file({ balance = {}, pin = {}, credit = {}, history = {} }, true)
else
f:close()
end
end
---------------------------------------------------
-- Public API accessible via `bank_accounts.*`
---------------------------------------------------
--- API ---
-- Gets all relevant data for a single player.
function bank_accounts.get_account_data(player_name)
local data = read_accounts_file()
-- Backwards compatibility check for old save files without a history table.
if not data.history then
data.history = {}
if not player_name then
return read_accounts_file()
end
local data = read_accounts_file()
if not data.history then data.history = {} end
return {
balance = data.balance[player_name] or 0,
pin = data.pin[player_name] or "0000",
@ -105,22 +72,19 @@ function bank_accounts.get_account_data(player_name)
}
end
-- Gets all data from the database (for background scripts like interest calculation).
function bank_accounts.get_all_data()
return read_accounts_file()
end
-- Saves the complete data object (for background scripts).
-- CORRECTED: save_all now calls the internal save function with the bypass parameter.
function bank_accounts.save_all(data)
return save_accounts_file(data)
return save_accounts_file(data, true)
end
-- Simple getter functions.
function bank_accounts.get_balance(player_name) local data = read_accounts_file(); return data.balance[player_name] or 0 end
function bank_accounts.get_credit(player_name) local data = read_accounts_file(); return data.credit[player_name] or 0 end
function bank_accounts.get_pin(player_name) local data = read_accounts_file(); return data.pin[player_name] or "0000" end
-- Adds a value to a player's balance and logs the transaction.
function bank_accounts.add_balance(player_name, amount, trans_type, purpose, other_party)
local data = read_accounts_file()
local current_balance = data.balance[player_name] or 0
@ -130,7 +94,6 @@ function bank_accounts.add_balance(player_name, amount, trans_type, purpose, oth
return save_accounts_file(data)
end
-- Sets a player's balance to an absolute value and logs the transaction.
function bank_accounts.set_balance(player_name, amount, trans_type, purpose, other_party)
local data = read_accounts_file()
local current_balance = data.balance[player_name] or 0
@ -141,7 +104,6 @@ function bank_accounts.set_balance(player_name, amount, trans_type, purpose, oth
return save_accounts_file(data)
end
-- Adds a value to a player's credit debt and logs the transaction.
function bank_accounts.add_credit(player_name, amount, trans_type, purpose, other_party)
local data = read_accounts_file()
local current_credit = data.credit[player_name] or 0
@ -151,7 +113,6 @@ function bank_accounts.add_credit(player_name, amount, trans_type, purpose, othe
return save_accounts_file(data)
end
-- Sets a player's credit debt to an absolute value and logs the transaction.
function bank_accounts.set_credit(player_name, amount, trans_type, purpose, other_party)
local data = read_accounts_file()
local current_credit = data.credit[player_name] or 0
@ -162,31 +123,24 @@ function bank_accounts.set_credit(player_name, amount, trans_type, purpose, othe
return save_accounts_file(data)
end
-- Sets a player's PIN.
function bank_accounts.set_pin(player_name, pin)
local data = read_accounts_file()
data.pin[player_name] = tostring(pin)
return save_accounts_file(data)
end
-- Checks if a player has an entry in the database.
function bank_accounts.player_has_account(player_name)
local data = read_accounts_file()
return data.balance[player_name] ~= nil
end
-- Creates a new, empty account for a player.
function bank_accounts.create_account(player_name)
if bank_accounts.player_has_account(player_name) then
return false
end
if bank_accounts.player_has_account(player_name) then return false end
local data = read_accounts_file()
data.balance[player_name] = 0
data.pin[player_name] = "0000"
data.credit[player_name] = 0
if not data.history then
data.history = {}
end
if not data.history then data.history = {} end
data.history[player_name] = {}
log_transaction(data, player_name, "system", 0, 0, "Account Created", "Initial account setup")
return save_accounts_file(data)

View file

@ -1,8 +1,8 @@
--[[
Internationalization (i18n) Setup
---------------------------------
Defines the global translation function S() for the mod.
It dynamically gets the mod's name to support renaming the mod folder.
--]]
S = minetest.get_translator(minetest.get_current_modname())
-- Internationalization (i18n) support
local mod_name = "bank_accounts"
if minetest.get_translator then
S = minetest.get_translator(minetest.get_current_modname())
else
-- Fallback for older Minetest versions
S = function(str) return str end
end

View file

@ -1,25 +1,17 @@
--[[
Bank Accounts Mod - Initialization File
---------------------------------------
This file defines the global namespace and loads all other
mod files in the correct order.
--]]
-- init.lua (Final & Sauber)
-- Global namespace for the mod to organize functions and data.
bank_accounts = {}
-- Load helper scripts first.
dofile(minetest.get_modpath("bank_accounts") .. "/i18n.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/functions.lua")
-- Load item and node definitions.
dofile(minetest.get_modpath("bank_accounts") .. "/cards.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/atm.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/atm2.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/computer.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/computer2.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/card_swipe.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/pin_terminal.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/wtm.lua")
-- Load systems that run in the background or provide commands.
dofile(minetest.get_modpath("bank_accounts") .. "/bank_safe.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/chatcommands.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/payroll.lua")
dofile(minetest.get_modpath("bank_accounts") .. "/interest.lua")

View file

@ -1,67 +1,105 @@
-- interest.lua (Nutzt die neue, dedizierte API-Funktion)
-- interest.lua (Deadlock problem fixed)
-- KONFIGURATION
local BALANCE_INTEREST_RATE = 0.005
local CREDIT_INTEREST_RATE = 0.03
local INTEREST_CHECK_INTERVAL = 600
local BALANCE_INTEREST_RATE = tonumber(minetest.settings:get("bank_accounts.interest_rate_balance")) or 0.005
local CREDIT_INTEREST_RATE = tonumber(minetest.settings:get("bank_accounts.interest_rate_credit")) or 0.03
local INTEREST_CHECK_INTERVAL = tonumber(minetest.settings:get("bank_accounts.interest_check_interval")) or 600
local ONE_DAY_IN_SECONDS = 86400
local timestamp_file_path = minetest.get_worldpath() .. "/bank_interest_timestamp.txt"
local function read_timestamp() local f = io.open(timestamp_file_path, "r"); if not f then return 0 end; local time = tonumber(f:read("*a")); f:close(); return time or 0 end
local function write_timestamp(time) local f = io.open(timestamp_file_path, "w"); if not f then return end; f:write(tostring(time)); f:close() end
local function read_timestamp()
local f = io.open(timestamp_file_path, "r")
if not f then return 0 end
local time = tonumber(f:read("*a"))
f:close()
return time or 0
end
local function write_timestamp(time)
local f = io.open(timestamp_file_path, "w")
if not f then return end
f:write(tostring(time))
f:close()
end
local time_since_last_check = 0
bank_accounts.is_calculating_interest = false
minetest.register_globalstep(function(dtime)
if not minetest.settings:get_bool("bank_accounts.interest_enabled", true) then
return
end
time_since_last_check = time_since_last_check + dtime
if time_since_last_check < INTEREST_CHECK_INTERVAL then return end
if time_since_last_check < INTEREST_CHECK_INTERVAL then
return
end
time_since_last_check = 0
local last_timestamp = read_timestamp()
if os.time() - last_timestamp >= ONE_DAY_IN_SECONDS then
if bank_accounts.is_calculating_interest then return end
if bank_accounts.is_calculating_interest then
return
end
minetest.log("action", "[bank_accounts] Starting daily interest calculation...")
bank_accounts.is_calculating_interest = true
-- KORREKTUR: Nutzt die neue, dedizierte Funktion
local data = bank_accounts.get_all_data()
local changes_made = false
-- Zinsen auf Guthaben
-- Calculate interest on positive balances
if BALANCE_INTEREST_RATE > 0 then
for player_name, balance in pairs(data.balance) do
if balance > 0 then
local interest = balance * BALANCE_INTEREST_RATE
data.balance[player_name] = balance + interest
if not data.history then data.history = {} end; if not data.history[player_name] then data.history[player_name] = {} end
table.insert(data.history[player_name], 1, { timestamp = os.time(), type = "Interest Paid", account = "balance", amount = interest, new_total = data.balance[player_name], desc = S("Daily interest (@1%)", BALANCE_INTEREST_RATE * 100), other = "Bank" })
if not data.history then data.history = {} end
if not data.history[player_name] then data.history[player_name] = {} end
table.insert(data.history[player_name], 1, {
timestamp = os.time(), type = "Interest Paid", account = "balance",
amount = interest, new_total = data.balance[player_name],
purpose = S("Daily interest (@1%)", BALANCE_INTEREST_RATE * 100), other = "Bank"
})
changes_made = true
end
end
end
-- Zinsen auf Kredit
-- Calculate interest on credit debt
if CREDIT_INTEREST_RATE > 0 then
for player_name, credit in pairs(data.credit) do
if credit > 0 then
local interest = credit * CREDIT_INTEREST_RATE
data.credit[player_name] = credit + interest
if not data.history then data.history = {} end; if not data.history[player_name] then data.history[player_name] = {} end
table.insert(data.history[player_name], 1, { timestamp = os.time(), type = "Interest Charged", account = "credit", amount = interest, new_total = data.credit[player_name], desc = S("Daily interest (@1%)", CREDIT_INTEREST_RATE * 100), other = "Bank" })
if not data.history then data.history = {} end
if not data.history[player_name] then data.history[player_name] = {} end
table.insert(data.history[player_name], 1, {
timestamp = os.time(), type = "Interest Charged", account = "credit",
amount = interest, new_total = data.credit[player_name],
purpose = S("Daily interest (@1%)", CREDIT_INTEREST_RATE * 100), other = "Bank"
})
changes_made = true
end
end
end
if changes_made then
bank_accounts.save_all(data)
minetest.log("action", "[bank_accounts] Daily interest calculation complete.")
-- CORRECTED: Now calls the API function that bypasses the lock
local success = bank_accounts.save_all(data)
if success then
minetest.log("action", "[bank_accounts] Daily interest calculation complete and saved.")
-- CORRECTED: Timestamp is only updated after a successful save.
write_timestamp(os.time())
else
minetest.log("error", "[bank_accounts] Saving interest data FAILED. Will retry later.")
end
else
-- If no changes were made, still update the timestamp to prevent re-running immediately.
write_timestamp(os.time())
end
write_timestamp(os.time())
bank_accounts.is_calculating_interest = false
end
end)

View file

@ -1,7 +1,6 @@
License: MIT
Copyright 2016 Trent Lasich
Copyright 2025 Rage87 (complete redo + extend)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -204,10 +204,6 @@ Transfer Sent=Überweisung (Gesendet)
Transfer Sent (Credit)=Überweisung (Kredit)
Transfer Received=Überweisung (Empfangen)
# Interest
Interest Paid=Zinsen (Guthaben)
Interest Charged=Zinsen (Kredit)
# PIN Terminal
PIN Terminal=PIN-Terminal
Welcome! Please set your personal 4-digit PIN.=Willkommen! Bitte lege eine persönliche 4-stellige PIN fest.
@ -227,3 +223,76 @@ Confirm New PIN:=Neue PIN bestätigen:
[PIN Terminal] Your old PIN is incorrect.=[PIN-Terminal] Deine alte PIN ist falsch.
[PIN Terminal] The new PINs do not match.=[PIN-Terminal] Die neuen PINs stimmen nicht überein.
[PIN Terminal] Your PIN has been successfully changed.=[PIN-Terminal] Deine PIN wurde erfolgreich geändert.
# Bank
[Bank] System is busy, please try again in a moment.=[Bank] Das System ist zur Zeit ausgelastet. Bitte gleich noch einmal versuchen.
# Zins-System (interest.lua)
Interest Paid=Zinsen (Guthaben)
Interest Charged=Zinsen (Kredit)
Daily interest (@1%)=Tägliche Zinsen (@1%)
# Lohn-System (payroll.lua)
You received your payroll of @1 MG. It has been deposited into your bank account.=Du hast deinen Lohn von @1 MG erhalten. Er wurde deinem Bankkonto gutgeschrieben.
Payroll=Lohnzahlung
Periodic payroll for activity=Arbeitslohn
# Bank Safe (bank_safe.lua)
Bank Safe=Banktresor
Bank Safe (Unassigned)=Banktresor (Nicht zugewiesen)
Bank Safe (Occupied)=Banktresor (Belegt)
Assign Safe Owner=Besitzer zuweisen
Player Name:=Spielername:
Set Owner=Besitzer festlegen
This safe has not been assigned to a player yet.=Dieser Tresor wurde noch keinem Spieler zugewiesen.
This safe is locked.=Dieser Tresor ist verschlossen.
You must enter a name.=Du musst einen Namen eingeben.
This player does not have a bank account and cannot be assigned a safe.=Dieser Spieler hat kein Bankkonto und kann keinen Tresor zugewiesen bekommen.
Safe at @1 successfully assigned to @2.=Tresor bei @1 erfolgreich an @2 zugewiesen.
Bank Safe (owned by: @1)=Banktresor (von: @1)
Manage Safe for @1=Tresor für @1 verwalten
Change Owner to:=Besitzer ändern zu:
Unrent (Clear Owner)=Entsperren (Besitzer entf.)
Ownership of safe at @1 has been cleared.=Besitzanspruch für Tresor bei @1 wurde entfernt.
Manage Safe=Tresor verwalten
[Safe] Please use your bank card to access.=[Tresor] Bitte benutze deine Bankkarte zum Öffnen.
[Safe] Invalid PIN.=[Tresor] Ungültige PIN.
Please enter your PIN to access the safe.=Bitte gib deine PIN ein, um den Tresor zu öffnen.
PIN:=PIN:
[Safe] Please use your bank card to claim a safe.=[Tresor] Bitte benutze deine Bankkarte, um einen Tresor zu beanspruchen.
Claim this safe for a one-time fee of @1 MG?=Diesen Tresor für eine einmalige Gebühr von @1 MG beanspruchen?
Claim=Beanspruchen
[Safe] You have successfully claimed this safe.=[Tresor] Du hast diesen Tresor erfolgreich beansprucht.
[Safe] You do not have enough money to claim this safe.=[Tresor] Du hast nicht genug Geld, um diesen Tresor zu beanspruchen.
Setup Fee=Einrichtungsgebühr
Bank Safe Setup=Einrichtung Banktresor
Assign or Claim Safe (0 MG)=Safe zuweisen (0 MG)
Assign to Player=Spieler zuweisen
Claim for myself=Mir selbst zuweisen
# ATM Mark II (atm2.lua)
ATM Mark II=Geldautomat Mark II
Place coins and notes in the slot below.=Münzen und Scheine in den Schlitz einwerfen.
Currently Deposited: @1 MG=Aktuell eingezahlt: @1 MG
Return=Abbrechen
Take All=Alles entnehmen
Please take your money (@1 MG):=Bitte entnimm dein Geld (@1 MG):
Note: Amount was rounded down to the nearest dispensable value.=Hinweis: Betrag wurde auf den nächstmöglichen Wert abgerundet.
# Teller Computer Mark II (computer2.lua)
Teller Computer Mark II=Bankschalter Mark II
Select Customer Account=Kundenkonto auswählen
Search by Name:=Suche nach Name:
Managing Account: @1=Verwalte Konto: @1
Back to Customer List=Zurück zur Kundenliste
Withdrawal=Auszahlung
Deposit for @1=Einzahlung für @1
[Bank] The deposit was cancelled and returned to your inventory.=[Bank] Die Einzahlung wurde abgebrochen und das Geld deinem Inventar hinzugefügt.
Teller: Give these items to @1 (@2 MG):=Kassierer: Gib diese Gegenstände an @1 (@2 MG):
Teller Withdraw Cancel=Auszahlung (Schalter) Storno
Cancelled by teller @1=Storniert durch Kassierer @1
[Bank] Withdrawn money was returned to the customer's account.=[Bank] Ausgezahltes Geld wurde dem Kundenkonto zurückgebucht.
Really reset PIN for @1?=PIN für @1 wirklich zurücksetzen?
Really wipe account for @1?=Konto von @1 wirklich leeren?
Yes=Ja
No=Nein

View file

@ -2,6 +2,6 @@ name = bank_accounts
title = Bank Accounts (Redo & Extended)
description = A complete overhaul of the classic bank_accounts mod. Adds ATMs, a Teller Computer, PIN Terminals, Card Swipes, and Wire Transfer Machines. Features a full account system with balance and credit, a detailed transaction history (account statement), and an automatic daily interest system.
author = Tmanyo, Rage87
version = 2037
version = 2038
license = MIT
depends = currency

69
payroll.lua Normal file
View file

@ -0,0 +1,69 @@
-- payroll.lua (Lohn für Arbeit mit direkter Einzahlung aufs Konto)
-- Read configuration from minetest.conf
local payroll_enabled = minetest.settings:get_bool("bank_accounts.payroll_enabled", true)
if not payroll_enabled then
return -- Do nothing if the feature is disabled
end
local creative_payroll_enabled = minetest.settings:get_bool("bank_accounts.creative_payroll_enabled", true)
local payroll_amount = tonumber(minetest.settings:get("bank_accounts.payroll_amount")) or 10
local payroll_period = tonumber(minetest.settings:get("bank_accounts.payroll_period")) or 720
-- This table holds players who are due for a payroll payment.
local players_payroll = {}
local timer = 0
-- This globalstep runs periodically and flags players as being due for payment.
-- It does not perform the payment itself.
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < payroll_period then
return
end
timer = 0
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
if creative_payroll_enabled then
players_payroll[name] = payroll_amount
minetest.log("info", "[Payroll] Accrued payroll for "..name)
else
local privs = minetest.get_player_privs(name)
if not (privs.creative or privs.give) then
players_payroll[name] = payroll_amount
minetest.log("info", "[Payroll] Accrued payroll for "..name)
end
end
end
end)
-- This function is triggered by player activity and "claims" the accrued payroll.
local function claim_payroll(player)
if not player or player.is_fake_player then
return
end
local name = player:get_player_name()
local amount_due = players_payroll[name]
-- Check if payroll is due and if the bank_accounts mod is available
if amount_due and amount_due > 0 and bank_accounts then
-- Deposit the payroll directly into the player's bank account via the API
local purpose_text = S("Periodic payroll for activity")
local success = bank_accounts.add_balance(name, amount_due, "Payroll", purpose_text, "System")
-- Only reset the payroll due flag if the deposit was successful
if success then
players_payroll[name] = nil
minetest.chat_send_player(name, S("You received your payroll of @1 MG. It has been deposited into your bank account.", tostring(amount_due)))
minetest.log("info", "[Payroll] Deposited payroll for "..name.." into bank account.")
end
end
end
-- Register the triggers for claiming the payroll.
minetest.register_on_dignode(function(pos, oldnode, digger) claim_payroll(digger) end)
minetest.register_on_placenode(function(pos, node, placer) claim_payroll(placer) end)
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv) claim_payroll(player) end)

View file

@ -1,133 +1,41 @@
--[[
PIN Terminal Node
-----------------
This file defines the PIN Terminal, which allows players to
set their initial PIN, change it, and request a new card.
It provides an immersive replacement for the /set_pin command.
--]]
-- pin_terminal.lua (Angepasst mit Fehler-Nachricht für besetztes System)
-- Shows the form for a new player to set their first PIN.
local function show_pin_create_form(player, error_msg)
local player_name = player:get_player_name()
local formspec = "size[8,5]" ..
"label[1,0.5;"..S("Welcome! Please set your personal 4-digit PIN.").."]" ..
"pwdfield[2,2;4,1;new_pin;"..S("New PIN:").."]" ..
"button_exit[3,4;2,1;confirm_new_pin;"..S("Confirm").."]"
if error_msg then
formspec = formspec .. "label[1,3;`"..minetest.formspec_escape(S(error_msg)).."`]" .. "style[label;color=red]"
end
minetest.show_formspec(player_name, "bank_accounts:pin_create", formspec)
local player_name = player:get_player_name(); local formspec = "size[8,5]" .. "label[1,0.5;"..S("Welcome! Please set your personal 4-digit PIN.").."]" .. "pwdfield[2,2;4,1;new_pin;"..S("New PIN:").."]" .. "button_exit[3,4;2,1;confirm_new_pin;"..S("Confirm").."]"; if error_msg then formspec = formspec .. "label[1,3;`"..minetest.formspec_escape(S(error_msg)).."`]" .. "style[label;color=red]" end; minetest.show_formspec(player_name, "bank_accounts:pin_create", formspec)
end
-- Shows the main menu for an existing player at the PIN terminal.
local function show_pin_options_form(player)
local player_name = player:get_player_name()
minetest.show_formspec(player_name, "bank_accounts:pin_options",
"size[8,6]" ..
"label[1,0.5;"..S("PIN & Card Service").."]" ..
"label[1,1.5;"..S("What would you like to do?").."]" ..
"button_exit[2,3;4,1;change_pin;"..S("Change PIN").."]" ..
"button_exit[2,4;4,1;new_card;"..S("Request New Card").."]")
end
-- Shows the form to change an existing PIN.
-- Can display an error message if something goes wrong.
local function show_pin_options_form(player) local player_name = player:get_player_name(); minetest.show_formspec(player_name, "bank_accounts:pin_options", "size[8,6]" .. "label[1,0.5;"..S("PIN & Card Service").."]" .. "label[1,1.5;"..S("What would you like to do?").."]" .. "button_exit[2,3;4,1;change_pin;"..S("Change PIN").."]" .. "button_exit[2,4;4,1;new_card;"..S("Request New Card").."]") end
local function show_pin_change_form(player, error_msg)
local player_name = player:get_player_name()
local formspec = "size[8,7]" ..
"label[1,0.5;"..S("Change Your PIN").."]" ..
"pwdfield[2,2;4,1;old_pin;"..S("Old PIN:").."]" ..
"pwdfield[2,3;4,1;new_pin1;"..S("New PIN:").."]" ..
"pwdfield[2,4;4,1;new_pin2;"..S("Confirm New PIN:").."]" ..
"button_exit[3,6;2,1;confirm_change;"..S("Confirm").."]"
if error_msg then
formspec = formspec .. "label[1,5;`"..minetest.formspec_escape(S(error_msg)).."`]" .. "style[label;color=red]"
end
minetest.show_formspec(player_name, "bank_accounts:pin_change", formspec)
local player_name = player:get_player_name(); local formspec = "size[8,7]" .. "label[1,0.5;"..S("Change Your PIN").."]" .. "pwdfield[2,2;4,1;old_pin;"..S("Old PIN:").."]" .. "pwdfield[2,3;4,1;new_pin1;"..S("New PIN:").."]" .. "pwdfield[2,4;4,1;new_pin2;"..S("Confirm New PIN:").."]" .. "button_exit[3,6;2,1;confirm_change;"..S("Confirm").."]"; if error_msg then formspec = formspec .. "label[1,5;`"..minetest.formspec_escape(S(error_msg)).."`]" .. "style[label;color=red]" end; minetest.show_formspec(player_name, "bank_accounts:pin_change", formspec)
end
-- Node definition for the PIN Terminal.
minetest.register_node("bank_accounts:pin_terminal", {
description = S("PIN Terminal"),
drawtype = "mesh",
mesh = "card_swipe.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"pin_terminal.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
-- Called when a player right-clicks the node.
-- It does not require a card to operate.
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
local player_name = player:get_player_name()
-- Check if the player is new (has the default PIN).
if bank_accounts.get_pin(player_name) == "0000" then
show_pin_create_form(player)
else
show_pin_options_form(player)
end
end,
description = S("PIN Terminal"), drawtype = "mesh", mesh = "card_swipe.obj", paramtype = "light", paramtype2 = "facedir", tiles = {"pin_terminal.png"}, groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
on_rightclick = function(pos, node, player, itemstack, pointed_thing) local player_name = player:get_player_name(); if bank_accounts.get_pin(player_name) == "0000" then show_pin_create_form(player) else show_pin_options_form(player) end end,
})
-- Handles formspec submissions for all PIN Terminal forms.
minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Only process forms belonging to this node.
if not formname:find("bank_accounts:pin_") then
return
end
if not formname:find("bank_accounts:pin_") then return end
local player_name = player:get_player_name()
-- Logic for initial PIN creation.
if formname == "bank_accounts:pin_create" then
if fields.confirm_new_pin then
local new_pin = fields.new_pin
if new_pin and new_pin:match("^[0-9][0-9][0-9][0-9]$") and new_pin ~= "0000" then
bank_accounts.set_pin(player_name, new_pin)
local inv = player:get_inventory()
if not inv:contains_item("main", "bank_accounts:atm_card") then
inv:add_item("main", "bank_accounts:atm_card")
end
if not bank_accounts.set_pin(player_name, new_pin) then minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment.")); show_pin_create_form(player, S("[Bank] System is busy, please try again in a moment.")); return end
local inv = player:get_inventory(); if not inv:contains_item("main", "bank_accounts:atm_card") then inv:add_item("main", "bank_accounts:atm_card") end
minetest.chat_send_player(player_name, S("[PIN Terminal] Your PIN has been set! Your new bank card is in your inventory."))
else
-- Re-show form with an error message.
show_pin_create_form(player, "[PIN Terminal] Your PIN must be 4 digits and not '0000'.")
else show_pin_create_form(player, "[PIN Terminal] Your PIN must be 4 digits and not '0000'.") end
end
end
-- Logic for the options menu.
elseif formname == "bank_accounts:pin_options" then
if fields.change_pin then
show_pin_change_form(player)
elseif fields.new_card then
local inv = player:get_inventory()
if inv:contains_item("main", "bank_accounts:atm_card") then
minetest.chat_send_player(player_name, S("[PIN Terminal] You already have a bank card."))
else
inv:add_item("main", "bank_accounts:atm_card")
minetest.chat_send_player(player_name, S("[PIN Terminal] A new bank card has been added to your inventory."))
end
end
-- Logic for the PIN change form.
if fields.change_pin then show_pin_change_form(player)
elseif fields.new_card then local inv = player:get_inventory(); if inv:contains_item("main", "bank_accounts:atm_card") then minetest.chat_send_player(player_name, S("[PIN Terminal] You already have a bank card.")) else inv:add_item("main", "bank_accounts:atm_card"); minetest.chat_send_player(player_name, S("[PIN Terminal] A new bank card has been added to your inventory.")) end end
elseif formname == "bank_accounts:pin_change" then
if fields.confirm_change then
local old_pin = fields.old_pin
local new_pin1 = fields.new_pin1
local new_pin2 = fields.new_pin2
if old_pin ~= bank_accounts.get_pin(player_name) then
show_pin_change_form(player, "[PIN Terminal] Your old PIN is incorrect.")
elseif not new_pin1 or not new_pin1:match("^[0-9][0-9][0-9][0-9]$") or new_pin1 == "0000" then
show_pin_change_form(player, "[PIN Terminal] Your PIN must be 4 digits and not '0000'.")
elseif new_pin1 ~= new_pin2 then
show_pin_change_form(player, "[PIN Terminal] The new PINs do not match.")
local old_pin = fields.old_pin; local new_pin1 = fields.new_pin1; local new_pin2 = fields.new_pin2
if old_pin ~= bank_accounts.get_pin(player_name) then show_pin_change_form(player, "[PIN Terminal] Your old PIN is incorrect.")
elseif not new_pin1 or not new_pin1:match("^[0-9][0-9][0-9][0-9]$") or new_pin1 == "0000" then show_pin_change_form(player, "[PIN Terminal] Your PIN must be 4 digits and not '0000'.")
elseif new_pin1 ~= new_pin2 then show_pin_change_form(player, "[PIN Terminal] The new PINs do not match.")
else
bank_accounts.set_pin(player_name, new_pin1)
if not bank_accounts.set_pin(player_name, new_pin1) then minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment.")); show_pin_change_form(player, S("[Bank] System is busy, please try again in a moment.")); return end
minetest.chat_send_player(player_name, S("[PIN Terminal] Your PIN has been successfully changed."))
end
end

11
settingtypes.txt Normal file
View file

@ -0,0 +1,11 @@
# Bank Accounts - Payroll Settings
bank_accounts.payroll_enabled (Enable Payroll System) bool true
bank_accounts.creative_payroll_enabled (Enable Payroll for Creative Players) bool true
bank_accounts.payroll_amount (Payroll Amount in MG) int 10 1 10000
bank_accounts.payroll_period (Payroll Period in Seconds) int 720 60 86400
# Bank Accounts - Interest Settings
bank_accounts.interest_enabled (Enable Daily Interest) bool true
bank_accounts.interest_rate_balance (Interest Rate on Balance e.g. 0.005) float 0.005
bank_accounts.interest_rate_credit (Interest Rate on Credit Debt e.g. 0.03) float 0.03
bank_accounts.interest_check_interval (Interest Check Interval in Seconds) int 600

BIN
textures/atm2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
textures/atm2_col.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
textures/atm2_col.xcf Normal file

Binary file not shown.

BIN
textures/atm2_uv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
textures/computer2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
textures/safe_side.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before After
Before After

BIN
textures/wtm_col.xcf Normal file

Binary file not shown.

165
wtm.lua
View file

@ -1,69 +1,27 @@
--[[
Wire Transfer Machine (WTM) Node
--------------------------------
This file defines the WTM, which allows players to securely transfer
money from their balance or credit line to another player's account.
--]]
-- wtm.lua (FINAL - Abgesichert gegen Race Conditions)
-- Uses the global 'pos_info' variable for consistency.
-- Die globale pos_info wird aus den anderen Dateien mitbenutzt.
-- Shows the account statement form.
-- This function is identical to the one in atm.lua but is kept here
-- in case of future WTM-specific modifications.
local function show_wtm_statement_form(player, account_type)
local player_name = player:get_player_name()
local data = bank_accounts.get_account_data(player_name)
local history = data.history
local lines = {}
local current_total_label = ""
local history = data.history; local lines = {}; local current_total_label = ""
table.insert(lines, minetest.formspec_escape(string.format("%-11s | %-13s | %-13s | %-20s | %-20s | %s", S("Date"), S("Amount"), S("New Balance"), S("Method"), S("Purpose"), S("Partner"))))
table.insert(lines, minetest.formspec_escape("-----------------------------------------------------------------------------------------"))
if history then
for _, t in ipairs(history) do
if t.account == account_type then
local parts = {}
table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp)))
table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG"))
table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG"))
table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = t.purpose or ""
local partner = t.other or ""
if purpose ~= "" or partner ~= "" then
table.insert(parts, string.format("%-20s", purpose))
if partner ~= "" then
table.insert(parts, partner)
end
end
if history then for _, t in ipairs(history) do if t.account == account_type then
local parts = {}; table.insert(parts, string.format("%-11s", os.date("%Y-%m-%d", t.timestamp))); table.insert(parts, string.format("%12s", string.format("%+.2f", t.amount) .. " MG")); table.insert(parts, string.format("%12s", string.format("%.2f", t.new_total).." MG")); table.insert(parts, string.format("%-20s", S(t.type)))
local purpose = t.purpose or ""; local partner = t.other or ""; if purpose ~= "" or partner ~= "" then table.insert(parts, string.format("%-20s", purpose)); if partner ~= "" then table.insert(parts, partner) end end
table.insert(lines, minetest.formspec_escape(table.concat(parts, " | ")))
end
end
end
if account_type == "balance" then
current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance) .. " MG")
else
current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit) .. " MG")
end
end end end
if account_type == "balance" then current_total_label = S("Current Balance: @1", string.format("%.2f", data.balance) .. " MG") else current_total_label = S("Current Credit Debt: @1", string.format("%.2f", data.credit) .. " MG") end
local form_name = "bank_accounts:wtm_statement@" .. player_name
local formspec = "size[13,9]"..
"label[0,0;"..S("Account Statement for @1", player_name).."]"..
"button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]"..
"button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]"..
"textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]"..
"label[0,8.4;"..current_total_label.."]"..
"button_exit[11,8.4;2,1;back;"..S("Back").."]"
local formspec = "size[13,9]".."label[0,0;"..S("Account Statement for @1", player_name).."]".."button_exit[0,0.5;2,1;view_balance;"..S("Balance").."]".."button_exit[2,0.5;2,1;view_credit;"..S("Credit").."]".."textlist[0,1.2;13,7;statement_list;"..table.concat(lines,",").."]".."label[0,8.4;"..current_total_label.."]".."button_exit[11,8.4;2,1;back;"..S("Back").."]"
minetest.show_formspec(player_name, form_name, formspec)
end
-- Shows the main menu for the WTM.
function wtm_main_form(player, pos)
local player_name = player:get_player_name()
local data = bank_accounts.get_account_data(player_name)
minetest.show_formspec(player_name, "bank_accounts:wtm_options",
"size[8,8]" ..
"button_exit[0.5,1;3.5,1;transfer_balance;"..S("Transfer from Balance").."]" ..
@ -76,17 +34,14 @@ function wtm_main_form(player, pos)
"button_exit[5,7;2,1;exit;"..S("Close").."]")
end
-- Node definition for the Wire Transfer Machine.
minetest.register_node("bank_accounts:wtm", {
description = S("Wire Transfer Machine"),
drawtype = "mesh",
mesh = "atm.obj", -- Uses ATM model as a base
mesh = "atm.obj",
paramtype = "light",
paramtype2 = "facedir",
tiles = {"wtm_col.png"}, -- Custom texture
tiles = {"wtm_col.png"},
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
-- Standard right-click function for PIN entry.
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
pos_info = pos
if itemstack:get_name() ~= "bank_accounts:atm_card" then
@ -94,104 +49,70 @@ minetest.register_node("bank_accounts:wtm", {
return
end
minetest.show_formspec(player:get_player_name(), "bank_accounts:wtm_home",
"size[8,8]" ..
"pwdfield[2,4;4,1;fourdigitpin;"..S("Four Digit Pin:").."]" ..
"button_exit[5,6;2,1;enter;"..S("Enter").."]" ..
"button_exit[3,6;2,1;exit;"..S("Cancel").."]")
"size[8,8]" .. "pwdfield[2,4;4,1;fourdigitpin;"..S("Four Digit Pin:").."]" ..
"button_exit[5,6;2,1;enter;"..S("Enter").."]" .. "button_exit[3,6;2,1;exit;"..S("Cancel").."]")
end,
})
-- Central handler for all formspec interactions related to the WTM.
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:find("bank_accounts:wtm") then
return
end
if not formname:find("bank_accounts:wtm") then return end
local player_name = player:get_player_name()
local pos = pos_info
if not pos then
return
end
if not pos then return end
-- Handle PIN entry form.
if formname == "bank_accounts:wtm_home" then
if fields.enter then
if bank_accounts.get_pin(player_name) == fields.fourdigitpin then
wtm_main_form(player, pos)
else
minetest.chat_send_player(player_name, S("[WTM] Invalid Pin."))
end
end
-- Handle main menu options.
if fields.enter then if bank_accounts.get_pin(player_name) == fields.fourdigitpin then wtm_main_form(player, pos) else minetest.chat_send_player(player_name, S("[WTM] Invalid Pin.")) end end
elseif formname == "bank_accounts:wtm_options" then
if fields.statement then
show_wtm_statement_form(player, "balance")
elseif fields.transfer_balance or fields.transfer_credit then
local source_account = fields.transfer_balance and "balance" or "credit"
local source_label = ""
if source_account == "balance" then
source_label = S("Transfer from Balance (Available: @1 MG)", string.format("%.2f", bank_accounts.get_balance(player_name)))
else
source_label = S("Transfer from Credit (Debt: @1 MG)", string.format("%.2f", bank_accounts.get_credit(player_name)))
end
if source_account == "balance" then source_label = S("Transfer from Balance (Available: @1 MG)", string.format("%.2f", bank_accounts.get_balance(player_name)))
else source_label = S("Transfer from Credit (Debt: @1 MG)", string.format("%.2f", bank_accounts.get_credit(player_name))) end
minetest.show_formspec(player_name, "bank_accounts:wtm_transfer@"..source_account,
"size[8,8]" ..
"label[0,0;"..source_label.."]" ..
"size[8,8]" .. "label[0,0;"..source_label.."]" ..
"field[0.5,1.5;7.5,1;recipient;"..S("Recipient:")..";]" ..
"field[0.5,2.5;7.5,1;amount;"..S("Amount:")..";]" ..
"field[0.5,3.5;7.5,1;purpose;"..S("Purpose:")..";]" ..
"button_exit[4,7;2,1;send;"..S("Send").."]" ..
"button_exit[2,7;2,1;cancel;"..S("Cancel").."]")
elseif fields.credit_card then
player:get_inventory():add_item("main", "bank_accounts:credit_card")
wtm_main_form(player, pos)
elseif fields.debit_card then
player:get_inventory():add_item("main", "bank_accounts:debit_card")
wtm_main_form(player, pos)
elseif fields.credit_card then player:get_inventory():add_item("main", "bank_accounts:credit_card"); wtm_main_form(player, pos)
elseif fields.debit_card then player:get_inventory():add_item("main", "bank_accounts:debit_card"); wtm_main_form(player, pos)
end
-- Handle the transfer form itself.
elseif formname:find("bank_accounts:wtm_transfer@") then
if fields.send then
local source_account = formname:match("bank_accounts:wtm_transfer@(.*)")
local recipient = fields.recipient
local amount = normalize_and_tonumber(fields.amount)
local purpose = fields.purpose
-- Validation checks
if not recipient or recipient == "" or not bank_accounts.player_has_account(recipient) then
minetest.chat_send_player(player_name, S("[WTM] Recipient not found or has no account."))
elseif not amount or amount <= 0 then
minetest.chat_send_player(player_name, S("[WTM] Invalid amount."))
elseif source_account == "balance" and bank_accounts.get_balance(player_name) < amount then
minetest.chat_send_player(player_name, S("[WTM] Insufficient funds."))
else
-- Process the transaction
if source_account == "balance" then
bank_accounts.add_balance(player_name, -amount, "Transfer Sent", purpose, recipient)
else -- source is "credit"
bank_accounts.add_credit(player_name, amount, "Transfer Sent (Credit)", purpose, recipient)
-- KORREKTUR: Prüfe auf das "Schloss", bevor die Transaktion beginnt
if bank_accounts.is_calculating_interest then
minetest.chat_send_player(player_name, S("[Bank] System is busy, please try again in a moment."))
wtm_main_form(player, pos)
return
end
local source_account = formname:match("bank_accounts:wtm_transfer@(.*)")
local recipient = fields.recipient; local amount = normalize_and_tonumber(fields.amount); local purpose = fields.purpose
if not recipient or recipient == "" or not bank_accounts.player_has_account(recipient) then minetest.chat_send_player(player_name, S("[WTM] Recipient not found or has no account."))
elseif not amount or amount <= 0 then minetest.chat_send_player(player_name, S("[WTM] Invalid amount."))
elseif source_account == "balance" and bank_accounts.get_balance(player_name) < amount then minetest.chat_send_player(player_name, S("[WTM] Insufficient funds."))
else
-- Da eine Überweisung zwei Schreibvorgänge erfordert, ist es hier am sichersten,
-- dass wir wissen, dass das System nicht beschäftigt ist.
if source_account == "balance" then
bank_accounts.add_balance(player_name, -amount, "Transfer Sent", purpose, recipient)
else
bank_accounts.add_credit(player_name, amount, "Transfer Sent (Credit)", purpose, recipient)
end
bank_accounts.add_balance(recipient, amount, "Transfer Received", purpose, player_name)
-- Send chat notifications
minetest.chat_send_player(player_name, S("[WTM] Successfully transferred @1 MG to @2.", string.format("%.2f", amount), recipient))
minetest.chat_send_player(recipient, S("[WTM] You received a transfer of @1 MG from @2.", string.format("%.2f", amount), player_name))
end
end
wtm_main_form(player, pos) -- Always return to the main menu
-- Handle the statement form.
elseif formname:find("bank_accounts:wtm_statement@") then
local target_name = formname:match("bank_accounts:wtm_statement@(.*)")
if not target_name then return end
if fields.back then
wtm_main_form(player, pos)
else
show_wtm_statement_form(player, fields.view_credit and "credit" or "balance")
end
elseif formname:find("bank_accounts:wtm_statement@") then
local target_name = formname:match("bank_accounts:wtm_statement@(.*)"); if not target_name then return end
if fields.back then wtm_main_form(player, pos) else show_wtm_statement_form(player, fields.view_credit and "credit" or "balance") end
end
end)