-- 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)