-- Copyright (C) 2021, 2023 Ale -- This file is part of Emeraldbank Minetest Mod. -- Emeraldbank is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as -- published by the Free Software Foundation, either version 3 of the -- License, or (at your option) any later version. -- Emeraldbank is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Affero General Public License for more details. -- You should have received a copy of the GNU Affero General Public License -- along with Emeraldbank. If not, see . local S = core.get_translator(core.get_current_modname()) function atm.ensure_init(name) -- Ensure the atm account for the placer specified by name exists atm.readaccounts() if not atm.balance[name] then atm.balance[name] = atm.startbalance end end -- banking accounts storage function atm.readaccounts () local b = atm.balance local file = io.open(atm.pth, "r") if file then repeat local balance = file:read("*n") if balance == nil then break end local name = file:read("*l") b[name:sub(2)] = balance until file:read(0) == nil io.close(file) end end function atm.saveaccounts() if not atm.balance then return end local data = {} for k, v in pairs(atm.balance) do table.insert(data, string.format("%d %s\n", v, k)) end local output = io.open(atm.pth, "w") output:write(table.concat(data)) io.close(output) end function emeraldbank.update_account() atm.readaccounts() for _, player in ipairs(core.get_connected_players()) do if not player or player.is_fake_player then return end local meta = player:get_meta() local bankemeralds = meta:get_int("emeraldbank:emerald") local name = player:get_player_name() if atm.balance[name] then atm.balance[name] = math.floor(atm.balance[name] + bankemeralds) else atm.balance[name] = bankemeralds end meta:set_int("emeraldbank:emerald", 0) -- backup meta:set_int("emeraldbank:emerald_backup", bankemeralds) end atm.saveaccounts() end function emeraldbank.add_emeralds(player, num) atm.readaccounts() if not player then return false end local meta = player:get_meta() local name = player:get_player_name() if num then if atm.balance[name] then atm.balance[name] = math.floor(atm.balance[name] + num) else atm.balance[name] = num end mcl_title.set(player, "actionbar", {text=S("Emeralds in Bank: @1", atm.balance[name]), color="yellow"}) atm.saveaccounts() return true end return false end function emeraldbank.transfer_emeralds(player1, player2, num) atm.readaccounts() local name = player1:get_player_name() local name2 = player2:get_player_name() local bankemeralds1 = atm.balance[name] if num > 0 then if bankemeralds1 and bankemeralds1 >= num then core.chat_send_player(name, S("Pay Successfully! You have transferred @1 Emeralds." , num)) core.chat_send_player(name2, S("Pay Successfully! You've gotten @1 Emeralds.", num)) if core.get_modpath("irc") then irc.say(name.." has transferred "..num.." emeralds to "..name2) end if core.get_modpath("yl_matterbridge") then yl_matterbridge.send_to_bridge("", name.." has transferred "..num.." emeralds to "..name2) end emeraldbank.add_emeralds(player1, -num) emeraldbank.add_emeralds(player2, num) core.sound_play("cash", { to_player = name2, gain = 1.0, fade = 0.0, pitch = 1.0, }) else core.chat_send_player(name, S("Not enough Emeralds in your account")) end else core.chat_send_player(name, S("Invalid pay")) end end -- wire transfer data storage function atm.read_transactions() local file = io.open(atm.pth_wt, "r") if file then local data = file:read("*all") atm.completed_transactions = minetest.deserialize(data) end end function atm.write_transactions() if not atm.completed_transactions then return end local file = io.open(atm.pth_wt, "w") local data = minetest.serialize(atm.completed_transactions) file:write(data) io.close(file) end function emeraldbank.upgrade_shop(pos) local oldnode = core.get_node(pos) local old_meta = core.get_meta(pos) local old_meta_table = core.get_meta(pos):to_table() local nodename = core.get_node(pos).name -- set the new shop node core.swap_node(pos, {name = "fancy_vend:player_vendor"}) -- setup the new shop node -- Set variables for access later (for various checks, etc.) local owner = old_meta:get_string("owner") or "" pos.y = pos.y + 1 local above_node = minetest.get_node(pos).name -- If node above is air or the display node, and it is not protected, attempt to place the vendor. If vendor sucessfully places, place display node above, otherwise alert the user if (minetest.registered_nodes[above_node].buildable_to or above_node == "fancy_vend:display_node") and not minetest.is_protected(pos, owner) then if above_node ~= "fancy_vend:display_node" then minetest.set_node(pos, minetest.registered_nodes["fancy_vend:display_node"]) end -- Set owner local meta = minetest.get_meta(pos) meta:set_string("owner", owner or "") -- Set default meta meta:set_string("log", minetest.serialize({"Vendor placed by "..owner,})) emeraldbank.reset_vendor_settings(pos) emeraldbank.refresh_vendor(pos) else minetest.chat_send_player(owner, S("Vendors require 2 nodes of space.")) end if minetest.get_modpath("pipeworks") then pipeworks.after_place(pos) end -- copy old metadata in new node core.get_meta(pos):from_table(old_meta_table) -- new node local node = core.get_node(pos) local meta = core.get_meta(pos) local count = meta:get_int("count") local price = meta:get_int("price") local shop_item = meta:get_string("shop_item") local minv = meta:get_inventory() local settings = emeraldbank.get_vendor_settings(pos) settings.input_item = "mcl_core:emerald" settings.input_item_qty = price settings.output_item = shop_item settings.output_item_qty = count emeraldbank.refresh_vendor(pos) end function atm.showform (player) atm.ensure_init(player:get_player_name()) local formspec = "size[9,8.75]".. "label[0.5,0;"..S("Your account balance: $@1", atm.balance[player:get_player_name()]).."]" .. "label[0.5,0.75;"..S("Deposit:").."]" .. -- "label[0.5,0.75;1s]" .. -- "label[1.5,0.75;5s]" .. -- "label[2.5,0.75;10s]" .. -- "label[3.5,0.75;50s]" .. -- "label[4.5,0.75;100s]" .. "label[7.5,0.75;"..S("Withdraw:").."]" .. -- "label[6.5,0.75;1s]" .. -- "label[7.5,0.75;5s]" .. -- "label[8.5,0.75;10s]" .. -- "label[9.5,0.75;50s]" .. -- "label[10.5,0.75;100s]" .. "item_image_button[0.5,1.25;1,1;".. "mcl_core:emerald" ..";i1;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[1.5,1.25;1,1;".. "currency:minegeld_5" ..";i5;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[2.5,1.25;1,1;".. "currency:minegeld_10" ..";i10;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[3.5,1.25;1,1;".. "currency:minegeld_50" ..";i50;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[4.5,1.25;1,1;".. "currency:minegeld_100" ..";i100;\n\n\b\b\b\b\b" .. "x1" .."]" .. "item_image_button[7.5,1.25;1,1;".. "mcl_core:emerald" ..";i-1;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[7.5,1.25;1,1;".. "currency:minegeld_5" ..";i-5;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[8.5,1.25;1,1;".. "currency:minegeld_10" ..";i-10;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[9.5,1.25;1,1;".. "currency:minegeld_50" ..";i-50;\n\n\b\b\b\b\b" .. "x1" .."]" .. -- "item_image_button[10.5,1.25;1,1;".. "currency:minegeld_100" ..";i-100;\n\n\b\b\b\b\b" .. "x1" .."]" .. "item_image_button[0.5,2.25;1,1;".. "mcl_core:emerald" ..";t10;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[1.5,2.25;1,1;".. "currency:minegeld_5" ..";t50;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[2.5,2.25;1,1;".. "currency:minegeld_10" ..";t100;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[3.5,2.25;1,1;".. "currency:minegeld_50" ..";t500;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[4.5,2.25;1,1;".. "currency:minegeld_100" ..";t1000;\n\n\b\b\b\b" .. "x10" .."]" .. "item_image_button[7.5,2.25;1,1;".. "mcl_core:emerald" ..";t-10;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[7.5,2.25;1,1;".. "currency:minegeld_5" ..";t-50;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[8.5,2.25;1,1;".. "currency:minegeld_10" ..";t-100;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[9.5,2.25;1,1;".. "currency:minegeld_50" ..";t-500;\n\n\b\b\b\b" .. "x10" .."]" .. -- "item_image_button[10.5,2.25;1,1;".. "currency:minegeld_100" ..";t-1000;\n\n\b\b\b\b" .. "x10" .."]" .. "item_image_button[0.5,3.25;1,1;".. "mcl_core:emerald" ..";c100;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[1.5,3.25;1,1;".. "currency:minegeld_5" ..";c500;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[2.5,3.25;1,1;".. "currency:minegeld_10" ..";c1000;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[3.5,3.25;1,1;".. "currency:minegeld_50" ..";c5000;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[4.5,3.25;1,1;".. "currency:minegeld_100" ..";c10000;\n\n\b\b\b" .. "x100" .."]" .. "item_image_button[7.5,3.25;1,1;".. "mcl_core:emerald" ..";c-100;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[7.5,3.25;1,1;".. "currency:minegeld_5" ..";c-500;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[8.5,3.25;1,1;".. "currency:minegeld_10" ..";c-1000;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[9.5,3.25;1,1;".. "currency:minegeld_50" ..";c-5000;\n\n\b\b\b" .. "x100" .."]" .. -- "item_image_button[10.5,3.25;1,1;".. "currency:minegeld_100" ..";c-10000;\n\n\b\b\b" .. "x100" .."]" .. -- "button_exit[5.5,3;1,2;Quit;Quit]" .. "button[3.5,3;2,1;wt;"..S("Wire Transfer").."]".. "list[current_player;main;0,4.5;9,3;9]".. mcl_formspec.get_itemslot_bg(0,4.5,9,3).. "list[current_player;main;0,7.74;9,1;]".. mcl_formspec.get_itemslot_bg(0,7.74,9,1).. "listring[current_player;main]" minetest.after((0.1), function(gui) return minetest.show_formspec(player:get_player_name(), "atm.form", gui) end, formspec) end -- wire transfer interface function atm.showform_wt (player) atm.ensure_init(player:get_player_name()) local formspec = "size[8,6]".. "button[5.75,0;2,1;transactions;"..S("Transactions >").."]" .. "label[2.5,0;"..S("Wire Transfer System").."]" .. "label[2,0.5;"..S("Your account balance: $@1", atm.balance[player:get_player_name()]).. "]" .. "field[0.5,1.5;5,1;dstn;"..S("Recepient:")..";]".. "field[6,1.5;2,1;amnt;"..S("Amount:")..";]".. "field[0.5,3;7.5,1;desc;"..S("Description:")..";]".. "button_exit[0.2,5;1,1;Quit;"..S("Quit").."]" .. "button[4.7,5;3,1;pay;"..S("Complete the payment").."]" minetest.after((0.1), function(gui) return minetest.show_formspec(player:get_player_name(), "atm.form.wt", gui) end, formspec) end function atm.showform_wtconf (player, dstn, amnt, desc) atm.ensure_init(player:get_player_name()) local formspec = "size[8,6]".. "label[2.5,0;"..S("Wire Transfer System").."]" .. "label[2,0.5;"..S("Your account balance: $@1", atm.balance[player:get_player_name()]).. "]" .. "label[2.5,1;"..S("TRANSACTION SUMMARY:").."]".. "label[0.5,1.5;"..S("Recepient:").." "..dstn.."]".. "label[0.5,2;"..S("Amount:").." " .. amnt .. "]".. "label[0.5,2.5;"..S("Description:").." " .. desc .. "]".. "button_exit[0.2,5;1,1;Quit;"..S("Quit").."]" .. "button[4.7,5;3,1;cnfrm;"..S("Confirm transfer").."]" minetest.after((0.1), function(gui) return minetest.show_formspec(player:get_player_name(), "atm.form.wtc", gui) end, formspec) end function atm.showform_wtlist (player, tlist) atm.ensure_init(player:get_player_name()) local textlist = '' if not tlist then textlist = S("no transactions registered").."\n" else for _, entry in ipairs(tlist) do textlist = textlist .. entry.date .. " $" .. entry.sum .. " " .. S("from") .. " " .. entry.from .. ": " .. entry.desc .. "\n" end end local formspec = "size[8,6]".. "button[5.75,0;2,1;transfer;"..S("< Transfer money").."]" .. "label[2.5,0;"..S("Wire Transfer System").."]" .. "label[2,0.5;"..S("Your account balance: $@1", atm.balance[player:get_player_name()]).. "]" .. "textarea[0.5,1.25;7.5,4;hst;"..S("Transaction list")..";" .. textlist .. "]" .. "button_exit[0.2,5;1,1;Quit;"..S("Quit").."]" .. "button[4.7,5;3,1;clr;"..S("Clear transactions").."]" minetest.after((0.1), function(gui) return minetest.show_formspec(player:get_player_name(), "atm.form.wtl", gui) end, formspec) end function emeraldbank.bank_receive_fields(player, form, pressed) -- ATMs if form == "atm.form" then local n = player:get_player_name() local transaction = { amount = 0, denomination = 0, count = 0 } local pinv=player:get_inventory() -- single note transactions for _,i in pairs({1, 5, 10, 50, 100, -1, -5, -10, -50, -100}) do if pressed["i"..i] then transaction.amount = i transaction.denomination = '_' .. math.abs(i) if transaction.denomination == '_1' then transaction.denomination = '' end transaction.count = ' ' .. 1 break end end -- 10x banknote transactions for _,t in pairs({10, 50, 100, 500, 1000, -10, -50, -100, -500, -1000}) do if pressed["t"..t] then transaction.amount = t transaction.denomination = '_' .. math.abs(t/10) if transaction.denomination == '_1' then transaction.denomination = '' end transaction.count = ' ' .. 10 break end end -- 100x banknote transactions for _,c in pairs({100, 500, 1000, 5000, 10000, -100, -500, -1000, -5000, -10000}) do if pressed["c"..c] then transaction.amount = c transaction.denomination = '_' .. math.abs(c/100) if transaction.denomination == '_1' then transaction.denomination = '' end transaction.count = ' ' .. 100 break end end -- someone hit exchange button if pressed.wt then atm.showform_wt(player) end if (atm.balance[n] + transaction.amount) < 0 then minetest.chat_send_player(n, S("Not enough money in your account")) transaction.amount = 0 end local item = "mcl_core:emerald" .. transaction.denomination .. transaction.count if transaction.amount < 0 then if pinv:room_for_item("main", item) then pinv:add_item("main", item) atm.balance[n] = atm.balance[n] + transaction.amount else minetest.chat_send_player(n, S("Not enough room in your inventory")) end elseif transaction.amount > 0 then if pinv:contains_item("main", item) then pinv:remove_item("main", item) atm.balance[n] = atm.balance[n] + transaction.amount else minetest.chat_send_player(n, S("Not enough money in your inventory")) end end atm.saveaccounts() if not pressed.Quit and not pressed.quit then atm.showform(player) end end end function emeraldbank.wt_receive_fields(player, form, pressed) -- Wire transfer terminals if form == "atm.form.wt" or form == "atm.form.wtc" or form == "atm.form.wtl" then local n = player:get_player_name() if not pressed.Quit and not pressed.quit then if form == "atm.form.wt" and pressed.transactions then -- transaction list (can be edited in the form, but than means nothing) atm.read_transactions() atm.showform_wtlist(player, atm.completed_transactions[n]) elseif form == "atm.form.wtl" and pressed.transfer then atm.showform_wt(player) elseif form == "atm.form.wtl" and pressed.clr then -- clear all transactions in the player's list atm.read_transactions() atm.completed_transactions[n] = nil atm.write_transactions() minetest.chat_send_player(n, S("Your transaction history has been cleared")) atm.showform_wtlist(player, atm.completed_transactions[n]) elseif form == "atm.form.wt" and pressed.pay then -- perform the checks of validity for wire transfer order -- if passed, store the data in a temporary table and show confirmation window if not atm.balance[pressed.dstn] then minetest.chat_send_player(n, S("The recepient <@1> is not registered in the banking system, aborting", pressed.dstn)) atm.showform_wt(player) elseif not string.match(pressed.amnt, '^[0-9]+$') then minetest.chat_send_player(n, S("Invalid amount <@1> : must be an integer number, aborting", pressed.amnt)) atm.showform_wt(player) elseif atm.balance[n] < tonumber(pressed.amnt) then minetest.chat_send_player(n, S("Your account does not have enough funds to complete this transfer, aborting")) atm.showform_wt(player) else atm.pending_transfers[n] = {to = pressed.dstn, sum = tonumber(pressed.amnt), desc = pressed.desc} atm.showform_wtconf(player, pressed.dstn, pressed.amnt, pressed.desc) end elseif form == "atm.form.wtc" then -- transaction processing atm.read_transactions() local t = atm.pending_transfers[n] if not t then return end if not atm.completed_transactions[t.to] then atm.completed_transactions[t.to] = {} end if atm.balance[n] < t.sum then -- you can never be too paranoid about the funds availaible minetest.chat_send_player(n, S("Your account does not have enough funds to complete this transfer, aborting")) if not t.extern then atm.showform_wt(player) else minetest.close_formspec(n, "atm.form.wtc") end return end table.insert(atm.completed_transactions[t.to], {date=os.date("%Y-%m-%d"), from=n, sum=t.sum, desc=t.desc}) atm.balance[n] = atm.balance[n] - t.sum atm.balance[t.to] = atm.balance[t.to] + t.sum atm.write_transactions() atm.saveaccounts() if minetest.get_modpath("mail") and mail.send then mail.send({ from = "Emerald Bank", to = t.to, --cc = "carbon, copy", --bcc = "blind, carbon, copy", subject = S("Payment of @1 emeralds from @2", t.sum, n), body = S("Good news!\n\n@1 has transferred @2 emeralds to your bank account using the Emerald Bank Transfer System.\n\nTransfer description:\n\n@3", n, t.sum, t.desc) }) end minetest.chat_send_player(n, S("Payment of @1 to @2 completed", t.sum, t.to)) minetest.chat_send_player(n, S("@1, thank you for choosing the Emerald Bank Transfer system", n)) if t.callback then -- run callbacks from mods t.callback(t) end if t.extern == true then -- Transfer was initiated by mod atm.pending_transfers[n] = nil minetest.close_formspec(n, "atm.form.wtc") return end atm.pending_transfers[n] = nil atm.showform_wt(player) end else -- clear the pending transaction of the player, just in case if atm.pending_transfers[n] then atm.pending_transfers[n] = nil end end end end