bank_accounts/atm2.lua
2025-08-22 02:10:35 +02:00

325 lines
17 KiB
Lua

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