You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

386 lines
14KB

  1. --
  2. -- Minetest lurkcoin mod - ATMs
  3. --
  4. -- © 2019 by luk3yx
  5. --
  6. local open_atms = {}
  7. local function e(text)
  8. return minetest.formspec_escape(tostring(text))
  9. end
  10. local formspecs = {}
  11. -- 0.4 compatibility
  12. lurkcoin.formspec_prepend = ''
  13. if minetest.get_modpath('default') and rawget(_G, 'default') and
  14. default.gui_bg and default.gui_bg_img and default.gui_slots then
  15. lurkcoin.formspec_prepend = default.gui_bg .. default.gui_bg_img ..
  16. default.gui_slots
  17. end
  18. -- The formspec code is based on something random I did in 2017(?) for
  19. -- lurkcoinV1, formspecs are weird and I somehow got it right™ then.
  20. local function get_formspec(name, page, params)
  21. -- The formspec template
  22. local formspec = 'size[8,9;]' .. lurkcoin.formspec_prepend ..
  23. 'label[0.5,1.75;Your balance: ' ..
  24. e(lurkcoin.bank.getbal(name)) .. 'cr.]' ..
  25. 'image_button[2,0.55;4,0.5;default_dirt.png^\\[colorize:#343434;y;' ..
  26. 'Welcome to a ' .. e(lurkcoin.server_name) .. ' ATM!]' ..
  27. 'label[0.5,2.25;Exchange rate: \194\1641.00 is equal to ' ..
  28. e(lurkcoin.exchange_rate) .. 'cr.]' ..
  29. 'image_button[1.75,1.05;4.5,0.5;default_dirt.png^\\[colorize:' ..
  30. '#343434;y; Your account: ' .. e(name) .. ']' ..
  31. 'image[0.5,0.5;1,1;default_mese_crystal.png]' ..
  32. 'image[6.5,0.5;1,1;default_mese_crystal.png]'
  33. -- Get the page formspec
  34. local page = formspecs[page] or formspecs.main
  35. if type(page) == 'string' then
  36. formspec = formspec .. page
  37. elseif type(page) == 'function' then
  38. formspec = formspec .. page(name, params)
  39. end
  40. -- Return the generated formspec
  41. return formspec
  42. end
  43. -- The formspecs (depending on installed mods)
  44. local withdrawls = false
  45. -- Payment screen
  46. function formspecs.pay(name, fields, guessed_amount)
  47. fields = fields or {}
  48. local formspec =
  49. 'field[0.8,3.5;7,1;user;User to pay;' .. e(fields.user or '') .. ']' ..
  50. 'field_close_on_enter[user;false]' ..
  51. 'field[0.8,4.8;7,1;server;Server the user is on;' ..
  52. e(fields.server or lurkcoin.server_name) .. ']' ..
  53. 'field_close_on_enter[server;false]' ..
  54. 'field[0.8,6.15;7,1;amount;Amount to pay (in cr);' ..
  55. e(fields.amount or '5.00') .. ']' ..
  56. 'field_close_on_enter[amount;false]'
  57. if fields._err then
  58. formspec = formspec ..
  59. 'label[0.5,7;' .. e(fields._err) .. ']' ..
  60. 'button[0.5,8;3.5,1;payuser;Cancel]' ..
  61. 'button[4,8;3.5,1;paysubmit;Try again]'
  62. elseif fields.paysubmit then
  63. formspec = formspec ..
  64. 'label[0.5,7;Please confirm the above values.'
  65. if fields.server ~= lurkcoin.server_name then
  66. local exc = -1
  67. if type(fields._exchange_rate) == 'number' then
  68. exc = fields._exchange_rate
  69. end
  70. if exc < 0 then
  71. lurkcoin.get_exchange_rate(fields.amount, fields.server or
  72. lurkcoin.server_name, function(data, msg)
  73. if not data then
  74. fields._err = msg
  75. end
  76. fields._exchange_rate = data
  77. return lurkcoin.show_atm(name, 'pay', fields)
  78. end)
  79. return formspec .. '\n' .. fields.amount .. 'cr is equal to' ..
  80. ' ...\n\n(Calculating...)]'
  81. end
  82. exc = tostring(math.floor(exc * 100) / 100)
  83. formspec = formspec .. '\n' .. fields.amount .. 'cr is ' ..
  84. 'equal to \194\164' .. exc .. '.'
  85. end
  86. formspec = formspec .. ']' ..
  87. 'button[0.5,8;3.5,1;payuser;Cancel]' ..
  88. 'button[4,8;3.5,1;payconfirm;Confirm and pay]'
  89. else
  90. formspec = formspec ..
  91. 'button[0.5,7;7,1;paysubmit;Pay the user!]'
  92. if withdrawls then
  93. formspec = formspec ..
  94. 'button[0.5,8;3.5,1;home;Go back]' ..
  95. 'button_exit[4,8;3.5,1;quit;Quit]'
  96. else
  97. formspec = formspec ..
  98. 'button_exit[0.5,8;7,1;quit;Quit]'
  99. end
  100. end
  101. return formspec
  102. end
  103. -- The main formspec
  104. if minetest.get_modpath('currency') then
  105. formspecs.main =
  106. 'button[0.5,3.25;7,1;deposit;Deposit money]' ..
  107. 'field[0.8,4.87;6,1;withdraw;Withdraw money (in Mg);5.00]' ..
  108. 'field_close_on_enter[withdraw;false]' ..
  109. 'button[6.5,4.55;1,1;wd;Next >]' ..
  110. 'field[0.8,6.12;6,1;user;Pay someone;]' ..
  111. 'field_close_on_enter[user;false]' ..
  112. 'button[6.5,5.8;1,1;payuser;Next >]' ..
  113. 'button_exit[0.5,8;7,1;quit;Quit]'
  114. -- Create a withdrawls list with available currency.
  115. withdrawls = {
  116. [0.05] = 'currency:minegeld_cent_5',
  117. [0.10] = 'currency:minegeld_cent_10',
  118. [0.25] = 'currency:minegeld_cent_25',
  119. [1] = 'currency:minegeld',
  120. [5] = 'currency:minegeld_5',
  121. [10] = 'currency:minegeld_10',
  122. [20] = 'currency:minegeld_20', -- The original mod now has 20Mg notes.
  123. [50] = 'currency:minegeld_50',
  124. [100] = 'currency:minegeld_100',
  125. }
  126. -- Remove non-registered notes
  127. for id, note in pairs(withdrawls) do
  128. if not minetest.registered_items[note] then
  129. withdrawls[id] = nil
  130. end
  131. end
  132. assert(#withdrawls > 0, 'The "currency" mod did not register any notes!')
  133. -- Create a detached inventory for depositing
  134. local inv = minetest.create_detached_inventory('lurkcoin:atm_deposit', {
  135. on_put = function(inv, listname, index, stack, player)
  136. local name = stack:get_name()
  137. if name:sub(1, 17) == 'currency:minegeld' then
  138. local m = name:sub(19)
  139. if #m > 0 then
  140. if m:sub(1, 5) == 'cent_' then
  141. m = tonumber(m:sub(6))
  142. if m then m = m / 100 end
  143. else
  144. m = tonumber(m)
  145. end
  146. else
  147. m = 1
  148. end
  149. if type(m) == 'number' and m == m then
  150. m = m * stack:get_count()
  151. local pname = player:get_player_name()
  152. if not lurkcoin.bank.add(pname, m, 'Deposit') then
  153. player:get_inventory():add_item('main', stack)
  154. end
  155. core.log('action', 'Player ' .. pname .. ' deposits ' ..
  156. tostring(m) .. 'Mg (' .. stack:to_string() ..
  157. ') into a lurkcoin ATM.')
  158. end
  159. end
  160. inv:set_list(listname, {})
  161. lurkcoin.show_atm(player:get_player_name(), 'deposit')
  162. end,
  163. allow_put = function(inv, listname, index, stack, player)
  164. local name = stack:get_name()
  165. if name:sub(1, 17) == 'currency:minegeld' then
  166. return stack:get_count()
  167. else
  168. return 0
  169. end
  170. end,
  171. allow_take = function() return 0 end,
  172. })
  173. inv:set_size("lurkcoin", 1)
  174. -- Depositing
  175. formspecs.deposit =
  176. 'list[current_player;main;0,4.85;8,1;]' ..
  177. 'list[current_player;main;0,6.08;8,3;8]' ..
  178. 'list[detached:lurkcoin:atm_deposit;lurkcoin;3.5,3;1,1;]' ..
  179. 'listring[]' ..
  180. 'image_button[0,3;3.5,1;default_dirt.png^\\[colorize:#343434;' ..
  181. 'ignore;Deposit money here →]' ..
  182. 'button[5.25,3;2,1;home;Finish]'
  183. else
  184. -- When there is no physical currency, the only thing you can do is pay
  185. -- others.
  186. formspecs.main = formspecs.pay
  187. end
  188. -- "Transaction accepted" screen
  189. function formspecs.success(name, text)
  190. return 'image[2.1,3;4.5,4.5;lurkcoin_success.png]' ..
  191. 'image_button[1,7;6,1;default_dirt.png^\\[colorize:#343434;' ..
  192. 'ignore;' .. e(text or 'Transaction sent!') .. ']' ..
  193. 'button[0.5,8;3.5,1;home;Go back]' ..
  194. 'button_exit[4,8;3.5,1;quit;Quit]'
  195. end
  196. -- "Transaction failed" screen
  197. function formspecs.error(name, text)
  198. return 'image[2.1,3;4.5,4.5;lurkcoin_error.png]' ..
  199. 'image_button[1,7;6,1;default_dirt.png^\\[colorize:#343434;' ..
  200. 'ignore;' .. e(text or 'An error has occurred!') .. ']' ..
  201. 'button[0.5,8;3.5,1;home;Go back]' ..
  202. 'button_exit[4,8;3.5,1;quit;Quit]'
  203. end
  204. -- Processing screen
  205. formspecs.processing =
  206. 'image[2.1,3;4.5,4.5;lurkcoin_processing.png]' ..
  207. 'image_button[1,7;6,1;default_dirt.png^\\[colorize:#343434;' ..
  208. 'ignore;Processing your transaction...]' ..
  209. 'image_button[1,8;6,1;default_dirt.png^\\[colorize:#343434;' ..
  210. 'ignore;This should only take a few seconds.]'
  211. -- A wrapper function
  212. function lurkcoin.show_atm(name, page, params)
  213. minetest.show_formspec(name, 'lurkcoin:atm', get_formspec(name,
  214. page, params))
  215. end
  216. minetest.register_on_player_receive_fields(function(player, formname, raw)
  217. if formname ~= 'lurkcoin:atm' then return end
  218. local name = player:get_player_name()
  219. -- Do a shallow copy in case some other mod relies on "fields" not changing.
  220. local fields = {}
  221. for k, v in pairs(raw) do
  222. if type(k) == 'string' and k:sub(1, 1) ~= '_' then
  223. fields[k] = v
  224. end
  225. end
  226. if withdrawls then
  227. if fields.deposit then
  228. return lurkcoin.show_atm(name, 'deposit')
  229. elseif fields.wd then
  230. local amount = tonumber(fields.withdraw)
  231. if not amount or amount ~= amount or amount <= 0 then
  232. return lurkcoin.show_atm(name, 'error',
  233. 'ERROR: Invalid number!')
  234. end
  235. local bal = lurkcoin.bank.getbal(name)
  236. if amount > bal then
  237. return lurkcoin.show_atm(name, 'error',
  238. 'ERROR: You cannot afford to do that!')
  239. end
  240. local note = false
  241. for id, item in pairs(withdrawls) do
  242. if (not note or id > note) and
  243. math.floor(amount / id) * id == amount then
  244. local def = minetest.registered_items[item]
  245. if def and amount / id <= (def.stack_max or 99) then
  246. note = id
  247. end
  248. end
  249. end
  250. if not note then
  251. return lurkcoin.show_atm(name, 'error',
  252. 'ERROR: I cannot store that amount of\nmoney in a ' ..
  253. 'single stack of notes/coins!')
  254. end
  255. local stack = ItemStack({
  256. name = withdrawls[note],
  257. count = amount / note,
  258. })
  259. local inv = player:get_inventory()
  260. if not inv:room_for_item('main', stack) then
  261. return lurkcoin.show_atm(name, 'error',
  262. 'ERROR: You do not have enough inventory space!')
  263. end
  264. minetest.log('action', 'Player ' .. name .. ' withdraws ' ..
  265. tostring(amount) .. 'Mg (' .. stack:to_string() ..
  266. ') from a lurkcoin ATM.')
  267. lurkcoin.bank.subtract(name, amount)
  268. inv:add_item('main', stack)
  269. return lurkcoin.show_atm(name, 'success')
  270. end
  271. end
  272. if fields.payconfirm and open_atms[name] and open_atms[name].pay then
  273. local data = open_atms[name]
  274. for k, v in pairs(data) do
  275. if type(v) == 'number' then fields[k] = tonumber(fields[k]) end
  276. if k ~= 'pay' and fields[k] ~= v then
  277. if k ~= 'amount' then
  278. k = 'target ' .. k
  279. end
  280. fields._err = 'The ' .. k ..
  281. ' was modified before pressing confirm!'
  282. lurkcoin.show_atm(name, 'pay', fields)
  283. return
  284. end
  285. end
  286. lurkcoin.show_atm(name, 'processing')
  287. lurkcoin.pay(name, data.user, data.server, data.amount,
  288. function(success, msg)
  289. local page = success and 'success' or 'error'
  290. return lurkcoin.show_atm(name, page, msg)
  291. end
  292. )
  293. elseif fields.payuser or fields.paysubmit or fields.payconfirm then
  294. if fields.paysubmit then
  295. local amount = tonumber(fields.amount)
  296. if not amount or amount ~= amount or amount <= 0 then
  297. fields._err = 'Invalid number!'
  298. lurkcoin.show_atm(name, 'pay', fields)
  299. return
  300. elseif lurkcoin.bank.getbal(name) - amount < 0 then
  301. fields._err = 'You cannot afford to do that!'
  302. lurkcoin.show_atm(name, 'pay', fields)
  303. return
  304. elseif fields.server then
  305. fields.server = fields.server:trim()
  306. end
  307. if fields.user then
  308. fields.user = fields.user:trim()
  309. end
  310. if not fields.server or fields.server == '' then
  311. fields.server = lurkcoin.server_name
  312. end
  313. open_atms[name] = {
  314. pay = true,
  315. user = fields.user or '',
  316. server = fields.server,
  317. amount = amount,
  318. }
  319. if open_atms[name].user == '' or (
  320. fields.server == lurkcoin.server_name and
  321. not lurkcoin.bank.user_exists(open_atms[name].user)
  322. ) then
  323. fields._err = 'That user does not exist!'
  324. lurkcoin.show_atm(name, 'pay', fields)
  325. return
  326. end
  327. end
  328. lurkcoin.show_atm(name, 'pay', fields)
  329. elseif fields.home then
  330. lurkcoin.show_atm(name, 'main')
  331. elseif fields.quit then
  332. open_atms[name] = nil
  333. end
  334. end)
  335. minetest.register_on_leaveplayer(function(player)
  336. -- TODO: I forgot what one register_on_leaveplayer uses and am too lazy to
  337. -- check.
  338. local name
  339. if type(player) == 'string' then
  340. name = player
  341. else
  342. name = player:get_player_name()
  343. end
  344. open_atms[name] = nil
  345. end)