Init repo with working copy from my private server
146
README.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Bank Accounts (Redo & Extended)
|
||||
|
||||
A complete overhaul and extension of the original `bank_accounts` mod by Tmanyo and Trent Lasich. This project has been restructured from the ground up to ensure stability, security, and a significantly expanded feature set.
|
||||
|
||||
## About This Version
|
||||
|
||||
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.
|
||||
|
||||
## Features Overview
|
||||
|
||||
* **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.
|
||||
|
||||
## Dependencies
|
||||
|
||||
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/)
|
||||
|
||||
## Known Issues & WIP
|
||||
|
||||
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.
|
||||
|
||||
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.**
|
||||
|
||||
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.
|
||||
|
||||
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 (Code) & Trent Lasich (Textures, Models)
|
||||
* Complete Redo, Extension & Bugfixing: **Rage87**
|
||||
322
atm.lua
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
--[[
|
||||
ATM Node
|
||||
--------
|
||||
This file defines the Automatic Teller Machine (ATM) node and all
|
||||
of its associated forms and logic for player interaction.
|
||||
--]]
|
||||
|
||||
-- A global variable to store the position of the last used machine.
|
||||
-- This is necessary for stability in the user's specific environment.
|
||||
pos_info = {}
|
||||
|
||||
-- The interest rate for the monthly credit payment.
|
||||
local credit_rate = 0.04
|
||||
|
||||
-- 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
|
||||
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.
|
||||
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").."]")
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
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))
|
||||
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
|
||||
bank_accounts.add_balance(player_name, -amount_to_pay, "Rate Payment", "", "")
|
||||
bank_accounts.add_credit(player_name, -amount_to_pay, "Rate Payment", "", "")
|
||||
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
|
||||
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
|
||||
end
|
||||
end)
|
||||
213
card_swipe.lua
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
--[[
|
||||
Card Swipe Node
|
||||
---------------
|
||||
This file defines the Card Swipe node, which allows players
|
||||
to set up shops and sell items via card payments.
|
||||
--]]
|
||||
|
||||
-- Uses the global 'pos_info' variable for consistency.
|
||||
|
||||
-- Node definition for the Card Swipe.
|
||||
minetest.register_node("bank_accounts:card_swipe", {
|
||||
description = S("Card Swipe"),
|
||||
drawtype = "mesh",
|
||||
mesh = "card_swipe.obj",
|
||||
paramtype = "light",
|
||||
paramtype2 = "facedir",
|
||||
tiles = {"card_reader_col.png"},
|
||||
groups = {cracky=3, crumbly=3, oddly_breakable_by_hand=2},
|
||||
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()
|
||||
meta:set_string("infotext", S("Card Swipe (owned by @1)", owner))
|
||||
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
|
||||
return meta:get_inventory():is_empty("items")
|
||||
end
|
||||
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.
|
||||
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]" ..
|
||||
"field[1,0.5;4,1;price;"..S("Price in MG:")..";"..minetest.formspec_escape(meta:get_string("price") or "").."]" ..
|
||||
"label[0,1.75;"..S("Items for Sale:").."]"..
|
||||
"list["..list_name..";items;0,2.25;8,2]" ..
|
||||
"label[0,4.5;"..S("Your Inventory:").."]"..
|
||||
"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."))
|
||||
return
|
||||
end
|
||||
local price_str = meta:get_string("price")
|
||||
if not price_str or price_str == "" then
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] No price has been set."))
|
||||
return
|
||||
end
|
||||
|
||||
local wielded_item = itemstack:get_name()
|
||||
if wielded_item ~= "bank_accounts:debit_card" and wielded_item ~= "bank_accounts:credit_card" then
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Must use a debit or credit card."))
|
||||
return
|
||||
end
|
||||
|
||||
if minetest.check_player_privs(player_name, {seized=true}) then
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Your account has been seized! Transaction denied."))
|
||||
return
|
||||
end
|
||||
|
||||
local price = 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
|
||||
end
|
||||
|
||||
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,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;]" ..
|
||||
"button_exit[2,7.4;2,1;cancel;"..S("Cancel").."]" ..
|
||||
"button_exit[4,7.4;2,1;buy;"..S("Buy").."]")
|
||||
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()
|
||||
end
|
||||
return 0
|
||||
end,
|
||||
|
||||
-- THEFT-PREVENTION: Players cannot take items manually.
|
||||
-- The script transfers them automatically upon successful purchase.
|
||||
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
return 0
|
||||
end,
|
||||
})
|
||||
|
||||
-- Crafting recipe for the Card Swipe machine.
|
||||
minetest.register_craft({
|
||||
output = "bank_accounts:card_swipe",
|
||||
recipe = {
|
||||
{"default:steel_ingot", "default:copper_ingot", "default:steel_ingot"},
|
||||
{"bank_accounts:credit_card", "default:mese", "bank_accounts:debit_card"},
|
||||
{"default:steel_ingot", "default:copper_ingot", "default:steel_ingot"},
|
||||
},
|
||||
})
|
||||
|
||||
-- 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
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
if not meta then
|
||||
return
|
||||
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)
|
||||
if price and price > 0 then
|
||||
meta:set_string("price", tostring(price))
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Price set to @1.", string.format("%.2f", price).." MG"))
|
||||
else
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Invalid price. Must be a number greater than 0."))
|
||||
end
|
||||
elseif fields.reset then
|
||||
meta:set_string("price", "")
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Price has been reset."))
|
||||
end
|
||||
end
|
||||
|
||||
-- Logic for the buyer's interface.
|
||||
if formname == "bank_accounts:card_swipe_buyer" then
|
||||
if fields.buy then
|
||||
local owner = meta:get_string("owner")
|
||||
local price = tonumber(meta:get_string("price") or "0")
|
||||
local shop_inv = meta:get_inventory()
|
||||
local wielded_item = player:get_wielded_item():get_name()
|
||||
|
||||
if price <= 0 or shop_inv:is_empty("items") then
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] This offer is no longer valid."))
|
||||
return
|
||||
end
|
||||
|
||||
local payment_successful = false
|
||||
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
|
||||
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
|
||||
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
|
||||
local stack = shop_inv:get_stack("items", i)
|
||||
if not stack:is_empty() then
|
||||
player_inv:add_item("main", stack)
|
||||
end
|
||||
end
|
||||
shop_inv:set_list("items", {})
|
||||
meta:set_string("price", "")
|
||||
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Purchase successful!"))
|
||||
minetest.chat_send_player(owner, S("[Card Swipe] Your items have been sold for @1.", string.format("%.2f", price).." MG"))
|
||||
player:get_inventory():add_item("main", "bank_accounts:receipt")
|
||||
else
|
||||
minetest.chat_send_player(player_name, S("[Card Swipe] Payment declined!"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
30
cards.lua
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
--[[
|
||||
Item Definitions
|
||||
----------------
|
||||
This file defines all physical items from the mod, like
|
||||
the different types of cards and the receipt.
|
||||
--]]
|
||||
|
||||
-- The credit card for purchases via credit line.
|
||||
minetest.register_craftitem("bank_accounts:credit_card", {
|
||||
description = S("Credit Card"),
|
||||
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"),
|
||||
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"),
|
||||
inventory_image = "receipt.png",
|
||||
groups = {not_in_creative_inventory=1},
|
||||
stack_max = 1,
|
||||
})
|
||||
212
chatcommands.lua
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
--[[
|
||||
Chat Commands
|
||||
-------------
|
||||
This file defines all chat commands for players and admins
|
||||
to interact with the banking system.
|
||||
--]]
|
||||
|
||||
-- Privilege for seized accounts.
|
||||
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)."),
|
||||
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 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"))
|
||||
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."),
|
||||
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
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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},
|
||||
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))
|
||||
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},
|
||||
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))
|
||||
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},
|
||||
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))
|
||||
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},
|
||||
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))
|
||||
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
|
||||
})
|
||||
265
computer.lua
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
--[[
|
||||
Teller Computer Node
|
||||
--------------------
|
||||
This file defines the Teller Computer node, which allows players with
|
||||
the 'bank_teller' privilege to manage other players' accounts.
|
||||
--]]
|
||||
|
||||
-- Uses the global 'pos_info' variable defined in atm.lua for consistency.
|
||||
pos_data = {} -- Kept for historical reasons, but pos_info is used.
|
||||
|
||||
-- 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}
|
||||
if customer_name and customer_name ~= "" and bank_accounts.player_has_account(customer_name) then
|
||||
data = bank_accounts.get_account_data(customer_name)
|
||||
end
|
||||
|
||||
local formspec = "size[8,9.5]" ..
|
||||
"label[0,-.25;"..S("Deposit:").."]" ..
|
||||
"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:")..";]" ..
|
||||
"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").."]" ..
|
||||
"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 = ""
|
||||
|
||||
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: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
|
||||
|
||||
-- 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,
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
if fields.stats then
|
||||
show_teller_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)
|
||||
elseif fields.reset_pin then
|
||||
bank_accounts.set_pin(customer_name, "0000")
|
||||
show_teller_form(player, pos, customer_name)
|
||||
elseif fields.enter then
|
||||
local meta=minetest.get_meta(pos)
|
||||
local inv=meta:get_inventory()
|
||||
|
||||
-- Deposit
|
||||
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)
|
||||
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."))
|
||||
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."))
|
||||
end
|
||||
end
|
||||
|
||||
-- Credit Payment
|
||||
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."))
|
||||
end
|
||||
else
|
||||
minetest.chat_send_player(player_name,S("[Bank] Player has insufficient funds for credit payment."))
|
||||
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
|
||||
end
|
||||
end)
|
||||
1
depends.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
currency
|
||||
193
functions.lua
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
--[[
|
||||
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.
|
||||
--]]
|
||||
|
||||
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
|
||||
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
|
||||
local data = f:read("*a")
|
||||
f:close()
|
||||
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)
|
||||
end
|
||||
|
||||
local f, err = io.open(file_path, "w")
|
||||
if not f then
|
||||
minetest.log("error", "[bank_accounts] Could not open accounts file for writing: " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
f:write(minetest.serialize(data))
|
||||
f:close()
|
||||
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.
|
||||
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.
|
||||
do
|
||||
local f = io.open(file_path, "r")
|
||||
if not f then
|
||||
save_accounts_file({ balance = {}, pin = {}, credit = {}, history = {} })
|
||||
else
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
---------------------------------------------------
|
||||
-- Public API accessible via `bank_accounts.*`
|
||||
---------------------------------------------------
|
||||
|
||||
-- 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 = {}
|
||||
end
|
||||
return {
|
||||
balance = data.balance[player_name] or 0,
|
||||
pin = data.pin[player_name] or "0000",
|
||||
credit = data.credit[player_name] or 0,
|
||||
history = data.history[player_name] or {},
|
||||
}
|
||||
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).
|
||||
function bank_accounts.save_all(data)
|
||||
return save_accounts_file(data)
|
||||
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
|
||||
local new_balance = current_balance + tonumber(amount)
|
||||
data.balance[player_name] = new_balance
|
||||
log_transaction(data, player_name, "balance", amount, new_balance, trans_type, purpose, other_party)
|
||||
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
|
||||
local new_balance = tonumber(amount)
|
||||
local diff = new_balance - current_balance
|
||||
data.balance[player_name] = new_balance
|
||||
log_transaction(data, player_name, "balance", diff, new_balance, trans_type or "admin_set", purpose, other_party)
|
||||
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
|
||||
local new_credit = current_credit + tonumber(amount)
|
||||
data.credit[player_name] = new_credit
|
||||
log_transaction(data, player_name, "credit", amount, new_credit, trans_type, purpose, other_party)
|
||||
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
|
||||
local new_credit = tonumber(amount)
|
||||
local diff = new_credit - current_credit
|
||||
data.credit[player_name] = new_credit
|
||||
log_transaction(data, player_name, "credit", diff, new_credit, trans_type or "admin_set_credit", purpose, other_party)
|
||||
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
|
||||
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
|
||||
data.history[player_name] = {}
|
||||
log_transaction(data, player_name, "system", 0, 0, "Account Created", "Initial account setup")
|
||||
return save_accounts_file(data)
|
||||
end
|
||||
8
i18n.lua
Normal file
|
|
@ -0,0 +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())
|
||||
25
init.lua
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
--[[
|
||||
Bank Accounts Mod - Initialization File
|
||||
---------------------------------------
|
||||
This file defines the global namespace and loads all other
|
||||
mod files in the correct order.
|
||||
--]]
|
||||
|
||||
-- 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") .. "/computer.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") .. "/chatcommands.lua")
|
||||
dofile(minetest.get_modpath("bank_accounts") .. "/interest.lua")
|
||||
67
interest.lua
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
-- interest.lua (Nutzt die neue, dedizierte API-Funktion)
|
||||
|
||||
-- KONFIGURATION
|
||||
local BALANCE_INTEREST_RATE = 0.005
|
||||
local CREDIT_INTEREST_RATE = 0.03
|
||||
local INTEREST_CHECK_INTERVAL = 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 time_since_last_check = 0
|
||||
bank_accounts.is_calculating_interest = false
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
time_since_last_check = time_since_last_check + dtime
|
||||
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
|
||||
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
|
||||
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" })
|
||||
changes_made = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Zinsen auf Kredit
|
||||
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" })
|
||||
changes_made = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if changes_made then
|
||||
bank_accounts.save_all(data)
|
||||
minetest.log("action", "[bank_accounts] Daily interest calculation complete.")
|
||||
end
|
||||
|
||||
write_timestamp(os.time())
|
||||
bank_accounts.is_calculating_interest = false
|
||||
end
|
||||
end)
|
||||
21
license.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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
|
||||
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.
|
||||
229
locale/bank_accounts.de.tr
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# textdomain: bank_accounts
|
||||
|
||||
# Items (cards.lua)
|
||||
ATM Card=Bankkarte
|
||||
Credit Card=Kreditkarte
|
||||
Debit Card=Debitkarte
|
||||
Receipt=Kassenbon
|
||||
|
||||
|
||||
# ATM, Computer & WTM (core.lua / atm.lua, computer.lua, wtm.lua)
|
||||
Automatic Teller Machine=Geldautomat
|
||||
[ATM] Must use ATM card.=[ATM] Bitte eine Bankkarte benutzen.
|
||||
Four Digit Pin:=Vierstellige PIN:
|
||||
Enter=Bestätigen
|
||||
Cancel=Abbrechen
|
||||
[ATM] Invalid Pin.=[ATM] Ungültige PIN.
|
||||
Withdraw=Auszahlung
|
||||
Deposit=Einzahlung
|
||||
Pay Credit Debt=Kredit zurückzahlen
|
||||
Account Balance: @1=Kontostand: @1
|
||||
Total Credit Debt: @1=Kreditsumme: @1
|
||||
Next Rate: @1=Nächste Rate: @1
|
||||
Get Credit Card=Kreditkarte anfordern
|
||||
Get Debit Card=Debitkarte anfordern
|
||||
Close=Schließen
|
||||
Amount:=Betrag:
|
||||
Amount to pay:=Zu zahlender Betrag:
|
||||
[ATM] Invalid amount.=[ATM] Ungültiger Betrag.
|
||||
[ATM] Funds Successfully Withdrawn!=[ATM] Geld erfolgreich ausgezahlt!
|
||||
Please take your money.=Bitte entnimm dein Geld.
|
||||
[ATM] Insufficient funds.=[ATM] Guthaben nicht ausreichend.
|
||||
[ATM] Deposited @1.=[ATM] @1 eingezahlt.
|
||||
[ATM] Withdrawn money has been moved to your inventory.=[ATM] Ausgezahltes Geld wurde in dein Inventar verschoben.
|
||||
[ATM] Please enter a PIN.=[ATM] Bitte eine PIN eingeben.
|
||||
[ATM] Insufficient funds or invalid amount.=[ATM] Guthaben nicht ausreichend oder ungültiger Betrag.
|
||||
[ATM] You don't have that much credit debt.=[ATM] Du hast keine so hohen Kreditschulden.
|
||||
[ATM] Paid @1 of credit debt.=[ATM] @1 der Kreditschulden bezahlt.
|
||||
[ATM] You must pay at least the minimum monthly rate.=[ATM] Die Zahlung muss mindestens der Rate entsprechen.
|
||||
Qualified Bank Teller=Qualifizierter Bankangestellter
|
||||
Bank Teller's Computer=Bankschalter-Computer
|
||||
Search:=Suche:
|
||||
Search=Suchen
|
||||
Player:=Spieler:
|
||||
Seized:=Gesperrt:
|
||||
[Bank] Insufficient privileges.=[Bank] Unzureichende Berechtigungen.
|
||||
Deposit:=Einzahlung:
|
||||
Withdraw:=Auszahlung:
|
||||
Credit Payment:=Kreditzahlung:
|
||||
Balance: @1=Kontostand: @1
|
||||
Credit Debt: @1=Kreditschulden: @1
|
||||
Show Account=Konto anzeigen
|
||||
Wipe Account=Konto leeren
|
||||
Reset PIN=PIN zurücksetzen
|
||||
No Results!=Keine Ergebnisse!
|
||||
[Bank] Must enter a player name.=[Bank] Spielername muss eingegeben werden.
|
||||
[Bank] Invalid player name entered.=[Bank] Ungültiger Spielername eingegeben.
|
||||
[Bank] Player's debt has been forgiven!=[Bank] Die Schulden des Spielers wurden erlassen!
|
||||
[Bank] Account successfully wiped!=[Bank] Konto erfolgreich geleert!
|
||||
[Bank] Your account has been wiped by teller @1.=[Bank] Dein Konto wurde von Kassierer @1 geleert.
|
||||
[Bank] Player's pin successfully reset!=[Bank] Die PIN des Spielers wurde erfolgreich zurückgesetzt!
|
||||
[Bank] Your PIN has been reset by a teller.=[Bank] Deine PIN wurde von einem Kassierer zurückgesetzt.
|
||||
[Bank] Deposited @1 for @2.=[Bank] @1 für @2 eingezahlt.
|
||||
[Bank] @1 MG was deposited into your account by teller @2. New balance: @3 MG=[Bank] @1 MG wurden von Kassierer @2 auf dein Konto eingezahlt. Neuer Kontostand: @3 MG
|
||||
[Bank] Withdrawal amount must be a whole number.=[Bank] Auszahlungsbetrag muss eine ganze Zahl sein.
|
||||
[Bank] Player has insufficient funds for withdrawal.=[Bank] Der Spieler hat nicht genug Guthaben für die Auszahlung.
|
||||
[Bank] @1 MG was withdrawn from your account by teller @2. New balance: @3 MG=[Bank] @1 MG wurden von Kassierer @2 von deinem Konto abgehoben. Neuer Kontostand: @3 MG
|
||||
[Bank] Credit payment must be a whole number.=[Bank] Kreditzahlung muss eine ganze Zahl sein.
|
||||
[Bank] Player does not have that much credit debt.=[Bank] Der Spieler hat keine so hohen Kreditschulden.
|
||||
[Bank] Player has insufficient funds for credit payment.=[Bank] Der Spieler hat nicht genug Guthaben für die Kreditzahlung.
|
||||
No Account=Kein Konto
|
||||
Yes=Ja
|
||||
No=Nein
|
||||
Wire Transfer Machine=Überweisungs-Maschine
|
||||
[WTM] Must use ATM card.=[WTM] Bitte eine Bankkarte benutzen.
|
||||
[WTM] Invalid Pin.=[WTM] Ungültige PIN.
|
||||
Transfer from Balance=Von Guthaben überweisen
|
||||
Transfer from Credit=Von Kredit überweisen
|
||||
Transfer from Balance (Available: @1 MG)=Von Guthaben überweisen (Verfügbar: @1 MG)
|
||||
Transfer from Credit (Debt: @1 MG)=Von Kredit überweisen (Schulden: @1 MG)
|
||||
Recipient:=Empfänger:
|
||||
Purpose:=Verwendungszweck:
|
||||
Send=Senden
|
||||
[WTM] Recipient not found or has no account.=[WTM] Empfänger nicht gefunden oder hat kein Konto.
|
||||
[WTM] Invalid amount.=[WTM] Ungültiger Betrag.
|
||||
[WTM] Insufficient funds.=[WTM] Guthaben nicht ausreichend.
|
||||
[WTM] Successfully transferred @1 MG to @2.=[WTM] Erfolgreich @1 MG an @2 überwiesen.
|
||||
[WTM] You received a transfer of @1 MG from @2.=[WTM] Du hast eine Überweisung über @1 MG von @2 erhalten.
|
||||
|
||||
|
||||
# Card Swipe (card_swipe.lua)
|
||||
Card Swipe=Kartenleser
|
||||
Card Swipe (owned by @1)=Kartenleser (im Besitz von @1)
|
||||
Price in MG:=Preis in MG:
|
||||
Items for Sale:=Zu verkaufen:
|
||||
Your Inventory:=Dein Inventar:
|
||||
Reset Price=Preis zurücksetzen
|
||||
Set Price=Preis festlegen
|
||||
[Card Swipe] No price has been set.=[Kartenleser] Es wurde kein Preis festgelegt.
|
||||
[Card Swipe] This machine is empty.=[Kartenleser] Dieses Gerät ist leer.
|
||||
[Card Swipe] Must use a debit or credit card.=[Kartenleser] Es muss eine EC- oder Kreditkarte verwendet werden.
|
||||
[Card Swipe] Your account has been seized! Transaction denied.=[Kartenleser] Dein Konto wurde gesperrt! Transaktion abgelehnt.
|
||||
[Card Swipe] Card declined. Insufficient funds.=[Kartenleser] Karte abgelehnt. Guthaben nicht ausreichend.
|
||||
[Card Swipe] A buyer tried to purchase your items but had insufficient funds.=[Kartenleser] Ein Käufer hatte nicht genug Geld für deine Items.
|
||||
Price: @1=Preis: @1
|
||||
Owner: @1=Besitzer: @1
|
||||
Items for Sale (Click 'Buy' to receive):=Zu verkaufen (Klicke 'Kaufen' zum Erhalten):
|
||||
Buy=Kaufen
|
||||
[Card Swipe] Invalid price. Must be a whole number greater than 0.=[Kartenleser] Ungültiger Preis. Muss eine ganze Zahl größer 0 sein.
|
||||
[Card Swipe] Invalid price. Must be a number greater than 0.=[Kartenleser] Ungültiger Preis. Muss eine Zahl größer 0 sein.
|
||||
[Card Swipe] Price has been reset.=[Kartenleser] Preis wurde zurückgesetzt.
|
||||
[Card Swipe] This offer is no longer valid.=[Kartenleser] Dieses Angebot ist nicht mehr gültig.
|
||||
[Card Swipe] Purchase successful!=[Kartenleser] Kauf erfolgreich!
|
||||
[Card Swipe] Your items have been sold for @1.=[Kartenleser] Deine Items wurden für @1 verkauft.
|
||||
[Card Swipe] Payment declined!=[Kartenleser] Zahlung abgelehnt!
|
||||
|
||||
|
||||
# Chat Commands (chatcommands.lua)
|
||||
Account seized.=Konto gesperrt.
|
||||
Shows your current account balance and credit debt.=Zeigt deinen aktuellen Kontostand und deine Kreditschulden.
|
||||
Shows your account balance or the balance of another player (if you have permission).=Zeigt deinen Kontostand oder den eines anderen Spielers an (falls du die Berechtigung hast).
|
||||
Set pin for your bank account.=Setzt die PIN für dein Bankkonto.
|
||||
[Account] No numbers entered.=[Konto] Keine Zahl eingegeben.
|
||||
[Account] Pin successfully set!=[Konto] PIN erfolgreich gesetzt!
|
||||
[Account] Invalid number entered. Must be exactly 4 digits.=[Konto] Ungültige Nummer. Muss genau 4 Ziffern lang sein.
|
||||
[Bank] Player name missing.=[Bank] Spielername fehlt.
|
||||
[Bank] No number entered.=[Bank] Keine Zahl eingegeben.
|
||||
[Bank] Invalid number entered.=[Bank] Ungültige Zahl eingegeben.
|
||||
[Bank] Amount must be a whole number.=[Bank] Betrag muss eine ganze Zahl sein.
|
||||
Add to a player's account balance.=Fügt dem Konto eines Spielers Geld hinzu.
|
||||
[Bank] Number must be greater than 0.=[Bank] Zahl muss größer als 0 sein.
|
||||
[Bank] @1 successfully added to @2's account.=[Bank] @1 wurde dem Konto von @2 erfolgreich hinzugefügt.
|
||||
Subtract from a player's account balance.=Zieht Geld vom Konto eines Spielers ab.
|
||||
[Bank] @1 successfully subtracted from @2's account.=[Bank] @1 wurde vom Konto von @2 erfolgreich abgezogen.
|
||||
Set a player's account balance.=Setzt den Kontostand eines Spielers.
|
||||
[Bank] Number must be 0 or greater.=[Bank] Zahl muss 0 oder größer sein.
|
||||
[Bank] Funds successfully set for @1.=[Bank] Kontostand für @1 erfolgreich gesetzt.
|
||||
Set a player's credit debt.=Setzt die Kreditschulden eines Spielers.
|
||||
[Bank] Credit debt for @1 has been set to @2.=[Bank] Kreditschulden für @1 wurden auf @2 gesetzt.
|
||||
Wipe a player's bank account.=Leert das Bankkonto eines Spielers.
|
||||
[Bank] Account successfully wiped for @1!=[Bank] Konto von @1 erfolgreich geleert!
|
||||
Seize a player's account.=Sperrt das Konto eines Spielers.
|
||||
[Bank] Account successfully seized for @1!=[Bank] Konto von @1 erfolgreich gesperrt!
|
||||
Unseize a player's account.=Entsperrt das Konto eines Spielers.
|
||||
[Bank] Account successfully unseized for @1!=[Bank] Konto von @1 erfolgreich entsperrt!
|
||||
Your Balance: @1=Dein Kontostand: @1
|
||||
Your Credit Debt: @1=Deine Kreditschulden: @1
|
||||
Balance for @1: @2=Kontostand für @1: @2
|
||||
Credit Debt for @1: @2=Kreditschulden für @1: @2
|
||||
Player '@1' not found or has no account.=Spieler '@1' nicht gefunden oder hat kein Konto.
|
||||
You do not have permission to view other players' accounts.=Du hast keine Berechtigung, die Konten anderer Spieler einzusehen.
|
||||
[<playername>]=[<Spielername>]
|
||||
<4-digit-pin>=<4-stellige-PIN>
|
||||
<name> <number>=<Name> <Zahl>
|
||||
<name>=<Name>
|
||||
|
||||
|
||||
# Kontoauszug
|
||||
Account Statement=Kontoauszug
|
||||
Date=Datum
|
||||
Amount=Betrag
|
||||
New Balance=Neuer Stand
|
||||
Method=Methode
|
||||
Purpose=Verwendungszweck
|
||||
Description=Beschreibung
|
||||
Partner=Partner
|
||||
Account Statement for @1=Kontoauszug für @1
|
||||
Balance=Guthaben
|
||||
Credit=Kredit
|
||||
Current Balance: @1=Aktueller Kontostand: @1
|
||||
Current Credit Debt: @1=Aktuelle Kreditschulden: @1
|
||||
Back=Zurück
|
||||
Pay Next Rate (@1 MG)=Nächste Rate zahlen (@1 MG)
|
||||
Or enter a custom amount:=Oder einen eigenen Betrag eingeben:
|
||||
Next calculated rate:=Nächste kalkulierte Rate:
|
||||
Pay Custom Amount=Eigenen Betrag zahlen
|
||||
|
||||
|
||||
# Transaktionstypen
|
||||
ATM Deposit=Einzahlung (ATM)
|
||||
ATM Withdrawal=Auszahlung (ATM)
|
||||
Credit Payment=Kreditzahlung
|
||||
Rate Payment=Ratenzahlung
|
||||
Teller Wipe=Kontoleerung (Schalter)
|
||||
Teller Deposit=Einzahlung (Schalter)
|
||||
Teller Withdrawal=Auszahlung (Schalter)
|
||||
Teller Credit Payment=Kreditzahlung (Schalter)
|
||||
Purchase=Kauf
|
||||
Sale=Verkauf
|
||||
Credit Purchase=Kauf (Kredit)
|
||||
Admin Add=Admin: Einzahlung
|
||||
Admin Subtract=Admin: Abhebung
|
||||
Admin Set Balance=Admin: Stand gesetzt
|
||||
Admin Set Credit=Admin: Kredit gesetzt
|
||||
Admin Wipe=Admin: Konto geleert
|
||||
By Admin: @1=Durch Admin: @1
|
||||
Teller: @1=Kassierer: @1
|
||||
Deposit by teller @1=Einzahlung durch Kassierer @1
|
||||
Withdrawal by teller @1=Auszahlung durch Kassierer @1
|
||||
Credit payment by teller @1=Kreditzahlung durch Kassierer @1
|
||||
Account wiped by teller @1=Konto geleert durch Kassierer @1
|
||||
Items from @1=Artikel von @1
|
||||
Items to @1=Artikel an @1
|
||||
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.
|
||||
New PIN:=Neue PIN:
|
||||
Confirm=Bestätigen
|
||||
[PIN Terminal] Your PIN must be 4 digits and not '0000'.=[PIN-Terminal] Deine PIN muss 4 Ziffern lang und darf nicht '0000' sein.
|
||||
[PIN Terminal] Your PIN has been set! Your new bank card is in your inventory.=[PIN-Terminal] Deine PIN wurde gesetzt! Deine neue Bankkarte ist in deinem Inventar.
|
||||
PIN & Card Service=PIN- & Karten-Service
|
||||
What would you like to do?=Was möchtest du tun?
|
||||
Change PIN=PIN ändern
|
||||
Request New Card=Neue Karte anfordern
|
||||
[PIN Terminal] You already have a bank card.=[PIN-Terminal] Du besitzt bereits eine Bankkarte.
|
||||
[PIN Terminal] A new bank card has been added to your inventory.=[PIN-Terminal] Eine neue Bankkarte wurde deinem Inventar hinzugefügt.
|
||||
Change Your PIN=Ändere deine PIN
|
||||
Old PIN:=Alte PIN:
|
||||
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.
|
||||
7
mod.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
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
|
||||
license = MIT
|
||||
depends = currency
|
||||
BIN
models/ATM.blend
Normal file
10
models/atm.mtl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl None
|
||||
Ns 0
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.8 0.8 0.8
|
||||
Ks 0.8 0.8 0.8
|
||||
d 1
|
||||
illum 2
|
||||
91
models/atm.obj
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Blender v2.75 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib atm.mtl
|
||||
o atm
|
||||
v 0.500000 -0.500000 -0.500000
|
||||
v 0.500000 0.200000 -0.200000
|
||||
v 0.500000 0.500000 0.300000
|
||||
v 0.500000 -0.500000 0.300000
|
||||
v -0.500000 -0.500000 0.300000
|
||||
v -0.500000 0.500000 0.300000
|
||||
v -0.500000 0.200000 -0.200000
|
||||
v -0.500000 -0.500000 -0.500000
|
||||
v 0.451229 -0.500000 -0.500000
|
||||
v 0.451229 -0.300000 -0.100000
|
||||
v 0.451229 0.450000 0.300000
|
||||
v -0.451229 0.450000 0.300000
|
||||
v -0.451229 -0.300000 -0.100000
|
||||
v -0.451229 -0.500000 -0.500000
|
||||
v 0.500000 0.500000 0.500000
|
||||
v 0.500000 -0.500000 0.500000
|
||||
v 0.451229 0.500000 0.500000
|
||||
v -0.451229 -0.500000 0.500000
|
||||
v -0.451229 0.500000 0.500000
|
||||
v -0.500000 0.500000 0.500000
|
||||
v -0.500000 -0.500000 0.500000
|
||||
v 0.451229 -0.500000 0.500000
|
||||
vt 0.625000 0.625000
|
||||
vt 0.875000 0.734375
|
||||
vt 0.984375 0.906250
|
||||
vt 0.625000 0.906250
|
||||
vt 0.515625 0.281250
|
||||
vt 0.359375 0.250000
|
||||
vt 0.343750 0.062500
|
||||
vt 0.515625 0.265625
|
||||
vt 0.156250 0.031250
|
||||
vt 0.140625 0.015625
|
||||
vt 0.140625 0.390625
|
||||
vt 0.156250 0.359375
|
||||
vt 0.359375 0.156250
|
||||
vt 0.343750 0.343750
|
||||
vt 0.390625 0.984375
|
||||
vt 0.015625 0.984375
|
||||
vt 0.015625 0.640625
|
||||
vt 0.390625 0.640625
|
||||
vt 0.515625 0.140625
|
||||
vt 0.515625 0.125000
|
||||
vt 0.390625 0.453125
|
||||
vt 0.015625 0.453125
|
||||
vt 0.984375 0.984375
|
||||
vt 0.625000 0.984375
|
||||
vt 0.078125 0.031250
|
||||
vt 0.640625 0.015625
|
||||
vt 0.640625 0.359375
|
||||
vt 0.625000 0.359375
|
||||
vt 0.625000 0.015625
|
||||
vt 0.984375 0.015625
|
||||
vt 0.984375 0.359375
|
||||
vt 0.968750 0.359375
|
||||
vt 0.968750 0.015625
|
||||
vt 0.078125 0.359375
|
||||
vt 0.078125 0.375000
|
||||
vn 1.000000 0.000000 0.000000
|
||||
vn -1.000000 -0.000000 -0.000000
|
||||
vn -0.969500 0.150400 -0.193400
|
||||
vn -0.975600 0.142700 -0.166500
|
||||
vn 0.975600 0.142700 -0.166500
|
||||
vn 0.000000 0.470600 -0.882400
|
||||
vn 0.969500 0.150400 -0.193400
|
||||
vn 0.000000 0.894400 -0.447200
|
||||
vn -0.453400 0.884400 -0.110600
|
||||
vn -0.000000 0.000000 1.000000
|
||||
vn 0.000000 0.970100 -0.242500
|
||||
vn 0.453400 0.884400 -0.110600
|
||||
usemtl None
|
||||
s 1
|
||||
f 1/1/1 2/2/1 3/3/1 4/4/1
|
||||
f 5/4/2 6/3/2 7/2/2 8/1/2
|
||||
f 9/5/3 10/6/3 2/7/3 1/8/3
|
||||
f 11/9/4 3/10/4 2/7/4 10/6/4
|
||||
f 6/11/5 12/12/5 13/13/5 7/14/5
|
||||
f 12/15/6 11/16/6 10/17/6 13/18/6
|
||||
f 8/19/7 7/14/7 13/13/7 14/20/7
|
||||
f 14/21/8 13/18/8 10/17/8 9/22/8
|
||||
f 4/4/1 3/3/1 15/23/1 16/24/1
|
||||
f 3/10/9 11/9/9 17/25/9 15/25/9
|
||||
f 18/26/10 19/27/10 20/28/10 21/29/10
|
||||
f 16/30/10 15/31/10 17/32/10 22/33/10
|
||||
f 22/33/10 17/32/10 19/27/10 18/26/10
|
||||
f 6/3/2 5/4/2 21/24/2 20/23/2
|
||||
f 11/9/11 12/12/11 19/34/11 17/25/11
|
||||
f 12/12/12 6/11/12 20/35/12 19/34/12
|
||||
10
models/card_swipe.mtl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl None
|
||||
Ns 0
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.8 0.8 0.8
|
||||
Ks 0.8 0.8 0.8
|
||||
d 1
|
||||
illum 2
|
||||
54
models/card_swipe.obj
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Blender v2.75 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib card_swipe.mtl
|
||||
o card_swipe
|
||||
v 0.250000 -0.500000 -0.250000
|
||||
v 0.250000 -0.300000 -0.250000
|
||||
v 0.250000 -0.300000 0.250000
|
||||
v 0.250000 -0.500000 0.250000
|
||||
v -0.250000 -0.300000 0.250000
|
||||
v -0.250000 -0.500000 0.250000
|
||||
v -0.250000 -0.300000 -0.250000
|
||||
v -0.250000 -0.500000 -0.250000
|
||||
v -0.350000 -0.500000 0.200000
|
||||
v -0.350000 -0.400000 0.200000
|
||||
v -0.350000 -0.400000 -0.200000
|
||||
v -0.350000 -0.500000 -0.200000
|
||||
v -0.250000 -0.400000 0.200000
|
||||
v -0.250000 -0.400000 -0.200000
|
||||
v -0.250000 -0.500000 0.200000
|
||||
v -0.250000 -0.500000 -0.200000
|
||||
vt 0.015625 0.718750
|
||||
vt 0.015625 0.531250
|
||||
vt 0.500000 0.531250
|
||||
vt 0.500000 0.718750
|
||||
vt 0.515625 0.500000
|
||||
vt 0.015625 0.500000
|
||||
vt 0.015625 0.015625
|
||||
vt 0.515625 0.015625
|
||||
vt 0.531250 0.125000
|
||||
vt 0.625000 0.125000
|
||||
vt 0.625000 0.515625
|
||||
vt 0.531250 0.515625
|
||||
vt 0.734375 0.125000
|
||||
vt 0.734375 0.515625
|
||||
vt 0.734375 0.015625
|
||||
vt 0.625000 0.015625
|
||||
vt 0.734375 0.625000
|
||||
vt 0.625000 0.625000
|
||||
vn 1.000000 0.000000 0.000000
|
||||
vn -0.000000 0.000000 1.000000
|
||||
vn -1.000000 -0.000000 -0.000000
|
||||
vn 0.000000 -0.000000 -1.000000
|
||||
vn 0.000000 1.000000 -0.000000
|
||||
usemtl None
|
||||
s 1
|
||||
f 1/1/1 2/2/1 3/3/1 4/4/1
|
||||
f 4/1/2 3/2/2 5/3/2 6/4/2
|
||||
f 6/1/3 5/2/3 7/3/3 8/4/3
|
||||
f 8/1/4 7/2/4 2/3/4 1/4/4
|
||||
f 5/5/5 3/6/5 2/7/5 7/8/5
|
||||
f 9/9/3 10/10/3 11/11/3 12/12/3
|
||||
f 13/13/5 14/14/5 11/11/5 10/10/5
|
||||
f 15/15/2 13/13/2 10/10/2 9/16/2
|
||||
f 14/14/4 16/17/4 12/18/4 11/11/4
|
||||
10
models/computer.mtl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl None
|
||||
Ns 0
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.8 0.8 0.8
|
||||
Ks 0.8 0.8 0.8
|
||||
d 1
|
||||
illum 2
|
||||
167
models/computer.obj
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# Blender v2.78 (sub 4) OBJ File: 'bank-computer.blend'
|
||||
# www.blender.org
|
||||
o Cube.001
|
||||
v 0.440909 -0.300000 -0.009091
|
||||
v 0.440909 0.300000 -0.009091
|
||||
v 0.440909 -0.300000 0.090909
|
||||
v 0.440909 0.300000 0.090909
|
||||
v -0.459091 -0.300000 -0.009091
|
||||
v -0.459091 0.300000 -0.009091
|
||||
v -0.459091 -0.300000 0.090909
|
||||
v -0.459091 0.300000 0.090909
|
||||
v 0.440909 -0.500000 -0.409091
|
||||
v 0.440909 -0.440000 -0.409091
|
||||
v 0.440909 -0.500000 -0.109091
|
||||
v 0.440909 -0.440000 -0.109091
|
||||
v -0.259091 -0.500000 -0.409091
|
||||
v -0.259091 -0.440000 -0.409091
|
||||
v -0.259091 -0.500000 -0.109091
|
||||
v -0.259091 -0.440000 -0.109091
|
||||
v -0.309091 -0.500000 -0.409091
|
||||
v -0.309091 -0.440000 -0.409091
|
||||
v -0.309091 -0.500000 -0.209091
|
||||
v -0.309091 -0.440000 -0.209091
|
||||
v -0.459091 -0.500000 -0.409091
|
||||
v -0.459091 -0.440000 -0.409091
|
||||
v -0.459091 -0.500000 -0.209091
|
||||
v -0.459091 -0.440000 -0.209091
|
||||
v 0.090909 -0.400000 0.090909
|
||||
v 0.090909 -0.100000 0.090909
|
||||
v 0.090909 -0.400000 0.190909
|
||||
v 0.090909 -0.100000 0.190909
|
||||
v -0.109091 -0.400000 0.090909
|
||||
v -0.109091 -0.100000 0.090909
|
||||
v -0.109091 -0.400000 0.190909
|
||||
v -0.109091 -0.100000 0.190909
|
||||
v 0.090909 -0.500000 0.190909
|
||||
v 0.090909 -0.500000 0.090909
|
||||
v -0.109091 -0.500000 0.190909
|
||||
v -0.109091 -0.500000 0.090909
|
||||
v -0.309091 -0.450000 -0.009091
|
||||
v -0.309091 -0.450000 0.090909
|
||||
v -0.309091 -0.500000 -0.009091
|
||||
v -0.309091 -0.500000 0.090909
|
||||
v 0.290909 -0.450000 0.090909
|
||||
v 0.290909 -0.450000 -0.009091
|
||||
v 0.290909 -0.500000 0.090909
|
||||
v 0.290909 -0.500000 -0.009091
|
||||
vt 0.066820 0.466486
|
||||
vt 0.666318 0.466486
|
||||
vt 0.666318 0.533096
|
||||
vt 0.066820 0.533097
|
||||
vt 0.666318 0.066820
|
||||
vt 0.732929 0.066820
|
||||
vt 0.732929 0.466486
|
||||
vt 0.066820 0.066820
|
||||
vt 0.000209 0.466486
|
||||
vt 0.000209 0.066820
|
||||
vt 0.066820 0.000209
|
||||
vt 0.666318 0.000209
|
||||
vt 0.000209 0.573481
|
||||
vt 0.040176 0.573481
|
||||
vt 0.040176 0.773314
|
||||
vt 0.000209 0.773314
|
||||
vt 0.040176 0.813281
|
||||
vt 0.506452 0.773314
|
||||
vt 0.506452 0.813280
|
||||
vt 0.546419 0.773314
|
||||
vt 0.506452 0.573481
|
||||
vt 0.546419 0.573481
|
||||
vt 0.506452 0.533515
|
||||
vt 0.040176 0.533515
|
||||
vt 0.913197 0.440259
|
||||
vt 0.873230 0.440259
|
||||
vt 0.873230 0.307037
|
||||
vt 0.913197 0.307037
|
||||
vt 0.873230 0.267071
|
||||
vt 0.773314 0.307037
|
||||
vt 0.773314 0.267071
|
||||
vt 0.733347 0.307037
|
||||
vt 0.773314 0.440259
|
||||
vt 0.733347 0.440259
|
||||
vt 0.773314 0.480226
|
||||
vt 0.873230 0.480226
|
||||
vt 0.799958 0.000209
|
||||
vt 0.799958 0.200042
|
||||
vt 0.733347 0.200042
|
||||
vt 0.733347 0.000209
|
||||
vt 0.695784 0.733348
|
||||
vt 0.695784 0.533515
|
||||
vt 0.829006 0.533515
|
||||
vt 0.829005 0.733348
|
||||
vt 0.999791 0.000209
|
||||
vt 0.999791 0.200042
|
||||
vt 0.933180 0.200042
|
||||
vt 0.933180 0.000209
|
||||
vt 0.829005 0.799958
|
||||
vt 0.695784 0.799958
|
||||
vt 0.933180 0.266653
|
||||
vt 0.799958 0.266653
|
||||
vt 0.546837 0.799958
|
||||
vt 0.546837 0.766653
|
||||
vt 0.328753 0.893736
|
||||
vt 0.328753 0.813699
|
||||
vt 0.466256 0.828528
|
||||
vt 0.449682 0.853351
|
||||
vt 0.168677 0.893736
|
||||
vt 0.168677 0.813699
|
||||
vt 0.467696 0.907275
|
||||
vt 0.504691 0.888922
|
||||
vt 0.322185 0.961134
|
||||
vt 0.977952 0.766653
|
||||
vt 0.977952 0.799958
|
||||
vt 0.051072 0.854687
|
||||
vt 0.029923 0.905485
|
||||
vt 0.000209 0.890334
|
||||
vt 0.030511 0.830905
|
||||
vt 0.174376 0.961042
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn -0.0000 0.0000 1.0000
|
||||
vn 1.0000 0.0000 -0.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 0.4472 0.0000 0.8944
|
||||
vn 0.4472 0.0000 -0.8944
|
||||
vn -0.2425 0.9701 0.0000
|
||||
vn -0.4472 0.0000 0.8944
|
||||
vn 0.2425 0.9701 0.0000
|
||||
vn -0.4472 0.0000 -0.8944
|
||||
g Cube.001_Cube.001_Material
|
||||
s off
|
||||
f 3/1/1 7/2/1 5/3/1 1/4/1
|
||||
f 7/2/2 8/5/2 6/6/2 5/7/2
|
||||
f 3/1/3 4/8/3 8/5/3 7/2/3
|
||||
f 1/9/4 2/10/4 4/8/4 3/1/4
|
||||
f 8/5/5 4/8/5 2/11/5 6/12/5
|
||||
f 9/13/4 10/14/4 12/15/4 11/16/4
|
||||
f 11/17/3 12/15/3 16/18/3 15/19/3
|
||||
f 15/20/2 16/18/2 14/21/2 13/22/2
|
||||
f 13/23/6 14/21/6 10/14/6 9/24/6
|
||||
f 16/18/5 12/15/5 10/14/5 14/21/5
|
||||
f 17/25/4 18/26/4 20/27/4 19/28/4
|
||||
f 19/29/3 20/27/3 24/30/3 23/31/3
|
||||
f 23/32/2 24/30/2 22/33/2 21/34/2
|
||||
f 21/35/6 22/33/6 18/26/6 17/36/6
|
||||
f 24/30/5 20/27/5 18/26/5 22/33/5
|
||||
f 25/37/4 26/38/4 28/39/4 27/40/4
|
||||
f 27/41/3 28/42/3 32/43/3 31/44/3
|
||||
f 31/45/2 32/46/2 30/47/2 29/48/2
|
||||
f 29/48/6 30/47/6 26/38/6 25/37/6
|
||||
f 27/41/3 31/44/3 35/49/3 33/50/3
|
||||
f 32/51/5 28/52/5 26/38/5 30/47/5
|
||||
f 27/41/7 33/50/7 43/53/7 41/54/7
|
||||
f 29/55/8 36/56/8 39/57/8 37/58/8
|
||||
f 29/55/6 25/59/6 34/60/6 36/56/6
|
||||
f 38/61/2 37/58/2 39/57/2 40/62/2
|
||||
f 31/63/9 29/55/9 37/58/9 38/61/9
|
||||
f 35/49/10 31/44/10 38/64/10 40/65/10
|
||||
f 42/66/4 41/67/4 43/68/4 44/69/4
|
||||
f 25/59/11 27/70/11 41/67/11 42/66/11
|
||||
f 34/60/12 25/59/12 42/66/12 44/69/12
|
||||
g Cube.001_Cube.001_Material.001
|
||||
f 5/71/6 6/72/6 2/73/6 1/74/6
|
||||
135
pin_terminal.lua
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
--[[
|
||||
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.
|
||||
--]]
|
||||
|
||||
-- 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)
|
||||
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_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)
|
||||
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,
|
||||
})
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
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'.")
|
||||
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.
|
||||
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.")
|
||||
else
|
||||
bank_accounts.set_pin(player_name, new_pin1)
|
||||
minetest.chat_send_player(player_name, S("[PIN Terminal] Your PIN has been successfully changed."))
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
BIN
textures/atm.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
textures/atm_card.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
textures/atm_col.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
textures/atm_col.png.old
Normal file
|
After Width: | Height: | Size: 629 B |
BIN
textures/atm_uv.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
textures/card_reader_col.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
textures/card_reader_uv.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
textures/computer.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
textures/computer_screen.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
textures/credit_card.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
textures/debit_card.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
textures/pin_terminal.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
textures/receipt.png
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
textures/wtm_col.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
textures/wtm_uv.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
197
wtm.lua
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
--[[
|
||||
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.
|
||||
--]]
|
||||
|
||||
-- Uses the global 'pos_info' variable for consistency.
|
||||
|
||||
-- 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 = ""
|
||||
|
||||
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: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").."]"
|
||||
|
||||
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").."]" ..
|
||||
"button_exit[0.5,2.0;3.5,1;transfer_credit;"..S("Transfer from Credit").."]" ..
|
||||
"button_exit[5,2.5;2.4,1;statement;"..S("Account Statement").."]" ..
|
||||
"label[5,1;"..S("Balance: @1", string.format("%.2f", data.balance) .. " MG").."]" ..
|
||||
"label[5,1.5;"..S("Credit Debt: @1", string.format("%.2f", data.credit) .. " MG").."]" ..
|
||||
"button_exit[0.5,6;3,1;credit_card;"..S("Get Credit Card").."]" ..
|
||||
"button_exit[0.5,7;3,1;debit_card;"..S("Get Debit Card").."]" ..
|
||||
"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
|
||||
paramtype = "light",
|
||||
paramtype2 = "facedir",
|
||||
tiles = {"wtm_col.png"}, -- Custom texture
|
||||
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
|
||||
minetest.chat_send_player(player:get_player_name(), S("[WTM] Must use ATM card."))
|
||||
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").."]")
|
||||
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
|
||||
|
||||
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: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.
|
||||
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
|
||||
minetest.show_formspec(player_name, "bank_accounts:wtm_transfer@"..source_account,
|
||||
"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)
|
||||
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)
|
||||
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
|
||||
end
|
||||
end)
|
||||