added new machines
190
README.md
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
16
cards.lua
|
|
@ -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,
|
||||
|
|
|
|||
216
chatcommands.lua
|
|
@ -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 })
|
||||
|
|
|
|||
258
computer.lua
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
16
i18n.lua
|
|
@ -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
|
||||
|
|
|
|||
18
init.lua
|
|
@ -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")
|
||||
|
|
|
|||
76
interest.lua
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
2
mod.conf
|
|
@ -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
|
|
@ -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)
|
||||
126
pin_terminal.lua
|
|
@ -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
|
|
@ -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
|
After Width: | Height: | Size: 26 KiB |
BIN
textures/atm2_col.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
textures/atm2_col.xcf
Normal file
BIN
textures/atm2_uv.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
textures/computer2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
textures/computer2_screen.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
textures/computer2_screen.xcf
Normal file
BIN
textures/safe_front_occupied.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
textures/safe_front_vacant.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
textures/safe_side.png
Normal file
|
After Width: | Height: | Size: 150 B |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.8 KiB |
BIN
textures/wtm_col.xcf
Normal file
165
wtm.lua
|
|
@ -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)
|
||||
|
|
|
|||