Electronic trading exchange in Minetest
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.

379 lines
11 KiB

  1. -- A telling machine. Call this file with the exchange argument.
  2. local exchange, formlib = ...
  3. local S = minetest.get_translator("global_exchange")
  4. local atm_form = "global_exchange:atm_form"
  5. local atm_pos = {}
  6. local unique = (function(unique_num)
  7. return function()
  8. unique_num = unique_num + 1
  9. return unique_num
  10. end
  11. end)(0)
  12. local coins = {
  13. [1] = "bitchange:minecoinblock", [2] = "bitchange:minecoin", [3] = "bitchange:mineninth",
  14. [4] = "maptools:gold_coin", [5] = "maptools:silver_coin", [6] = "maptools:copper_coin"
  15. }
  16. local coins_convert = {
  17. [coins[6]]=1, [coins[5]]=9, [coins[4]]=81, [coins[3]]=729, [coins[2]]=6561, [coins[1]]=59049,
  18. ["minercantile:copper_coin"]=1, ["minercantile:silver_coin"]=9, ["minercantile:gold_coin"]=81
  19. }
  20. local function withdraw_fs(fs, p_name, amount)
  21. local spos = atm_pos[p_name].x..","..atm_pos[p_name].y..","..atm_pos[p_name].z
  22. local inv = minetest.get_inventory({ type = "node", pos={x=atm_pos[p_name].x, y=atm_pos[p_name].y, z=atm_pos[p_name].z} })
  23. local msg = ""
  24. local w_amount = tonumber(amount)
  25. if w_amount and math.floor(w_amount) == w_amount and w_amount > 0 then
  26. local succ, err = exchange:give_credits(p_name, 0 - w_amount, S("Cash withdrawal: (-@1)", w_amount))
  27. if succ then
  28. local index = 1
  29. repeat
  30. local m = math.floor(w_amount / coins_convert[coins[index]])
  31. if m > 0 then
  32. inv:add_item( "main", ItemStack({ name = coins[index], count = m }) )
  33. end
  34. w_amount = w_amount - (coins_convert[coins[index]] * m)
  35. index = index + 1
  36. until ( w_amount == 0)
  37. else
  38. msg = err
  39. end
  40. elseif w_amount then
  41. msg = S("Invalid number ! Must be an Integer > 0")
  42. end
  43. local balance = exchange:get_balance(p_name)
  44. fs:size(8,10)
  45. if not balance then
  46. fs:label(0.5, 0.5, S("You don't have an account."))
  47. else
  48. fs:label(3,8.9, S("Balance: @1", balance))
  49. fs:field(0.75, 1.25, 3.25, 1, "w_amount", S("Desired amount:"))
  50. fs:button(4, 1, 3.25, 1, "withdraw", S("Get !"))
  51. fs:label(1, 2.25, S("Or deposit your coins to credit your account:"))
  52. fs:list(1,3,6,1, "nodemeta:"..spos, "main")
  53. fs:list(0,4.25, 8,4, "current_player", "main")
  54. fs("listring[current_player;main]"..
  55. "listring[nodemeta:"..spos..";main]"
  56. )
  57. fs:label(0,9.75, msg)
  58. end
  59. fs:button(0,8.75, 2,1, "logout", S("Log Out"))
  60. end
  61. local function info_fs(fs, p_name)
  62. local balance = exchange:get_balance(p_name)
  63. fs:size(4,3)
  64. if balance then
  65. fs:label(0.5,0.5, S("Balance: @1", balance))
  66. else
  67. fs:label(0.5,0.5, S("You don't have an account."))
  68. end
  69. fs:button(1,2, 2,1, "logout", S("Log Out"))
  70. end
  71. local function wire_fs(fs, p_name)
  72. local balance = exchange:get_balance(p_name)
  73. fs:size(4,5)
  74. if balance then
  75. -- To detect duplicate/stale form submission
  76. fs:field(-100, -100, 0,0, "trans_id", "", unique())
  77. fs:label(0.50,0.325, S("Balance: @1", balance))
  78. fs:field(0.75,1.750, 3,1, "recipient", S("Send to:"), "")
  79. fs:field(0.75,3.000, 3,1, "amount", S("Amount"), "")
  80. fs:button(0,4.25, 2,1, "logout", S("Log Out"))
  81. fs:button(2,4.25, 2,1, "send", S("Send"))
  82. else
  83. fs:button(0,4, 2,1, "logout", S("Back"))
  84. fs:label(0.5,0.5, S("You don't have an account."))
  85. end
  86. end
  87. local function send_fs(fs, p_name, receiver, amt_str)
  88. fs:size(10,3)
  89. fs:button(4,2, 2,1, "wire", S("Back"))
  90. local amt = tonumber(amt_str)
  91. local msg = nil
  92. if not amt or amt <= 0 then
  93. msg = S("Invalid transfer amount.")
  94. else
  95. local succ, err = exchange:transfer_credits(p_name, receiver, amt)
  96. if not succ then
  97. msg = "Error: " .. err
  98. else
  99. msg = "Successfully sent " .. amt ..
  100. " credits to " .. receiver .. "."
  101. end
  102. end
  103. fs:label(0.5,0.5, msg)
  104. end
  105. local function log_fs(fs, p_name)
  106. fs:size(14,8)
  107. fs:label(0,0, S("Transaction Log"))
  108. fs:element("tablecolumns", "text", "text")
  109. fs("table[0,0.75;13.75,6.75;log_table;Time,Message")
  110. for _, entry in ipairs(exchange:player_log(p_name)) do
  111. fs(","):escape_list(entry.Time, entry.Message)
  112. end
  113. fs("]")
  114. fs:button(6,7.5, 2,1, "logout", S("Log Out"))
  115. end
  116. local function main_menu_fs(fs, p_name)
  117. fs:size(8,2)
  118. fs:button(0,0.125, 4,1, "withdraw", S("Cash deposit and withdrawal"))
  119. fs:button(4,0.125, 2,1, "info", S("Account Info"))
  120. fs:button(6,0.125, 2,1, "wire", S("Wire Monies"))
  121. fs:button(0.50, 1.125, 7, 1, "transaction_log", S("Transaction Log"))
  122. end
  123. local function show_atm_form(fs_fn, p_name, ...)
  124. local fs = formlib.Builder()
  125. fs_fn(fs, p_name, ...)
  126. minetest.show_formspec(p_name, atm_form, tostring(fs))
  127. end
  128. local trans_ids = {}
  129. minetest.register_on_player_receive_fields(function(player, formname, fields)
  130. if formname ~= atm_form then return end
  131. if fields.quit then return true end
  132. local p_name = player:get_player_name()
  133. local this_id = tonumber(fields.trans_id)
  134. if this_id and trans_ids[p_name] and this_id <= trans_ids[p_name] then
  135. -- Ignore duplicate/stale form submittal
  136. return true
  137. end
  138. trans_ids[p_name] = this_id
  139. if fields.logout then
  140. show_atm_form(main_menu_fs, p_name)
  141. elseif fields.info then
  142. show_atm_form(info_fs, p_name)
  143. elseif fields.wire then
  144. show_atm_form(wire_fs, p_name)
  145. elseif fields.withdraw then
  146. show_atm_form(withdraw_fs, p_name, fields and fields.w_amount)
  147. elseif fields.send then
  148. show_atm_form(send_fs, p_name, fields.recipient, fields.amount)
  149. elseif fields.transaction_log then
  150. show_atm_form(log_fs, p_name)
  151. end
  152. return true
  153. end)
  154. minetest.register_node("global_exchange:atm_bottom", {
  155. description = "ATM",
  156. inventory_image = "global_exchange_atm_icon.png",
  157. wield_image = "global_exchange_atm_hi_front.png",
  158. drawtype = "nodebox",
  159. tiles = {
  160. "global_exchange_atm_lo_top.png",
  161. "global_exchange_atm_side.png",
  162. "global_exchange_atm_side.png",
  163. "global_exchange_atm_side.png",
  164. "global_exchange_atm_back.png^[transform2",
  165. "global_exchange_atm_lo_front.png",
  166. },
  167. paramtype = "light",
  168. paramtype2 = "facedir",
  169. is_ground_content = false,
  170. stack_max = 1,
  171. light_source = 3,
  172. node_box = {
  173. type = "fixed",
  174. fixed = {
  175. {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
  176. }
  177. },
  178. selection_box = {
  179. type = "fixed",
  180. fixed = {
  181. {-0.500, -0.500, -0.5000, 0.500, 0.500, 0.50},
  182. {-0.500, 0.500, -0.5000, -0.375, 1.125, -0.25},
  183. { 0.375, 0.500, -0.5000, 0.500, 1.125, -0.25},
  184. {-0.500, 0.500, -0.2500, 0.500, 1.500, 0.50},
  185. {-0.500, 1.125, -0.4375, -0.375, 1.250, -0.25},
  186. { 0.375, 1.125, -0.4375, 0.500, 1.250, -0.25},
  187. {-0.500, 1.250, -0.3750, -0.375, 1.375, -0.25},
  188. { 0.375, 1.250, -0.3750, 0.500, 1.375, -0.25},
  189. {-0.500, 1.375, -0.3125, -0.375, 1.500, -0.25},
  190. { 0.375, 1.375, -0.3125, 0.500, 1.500, -0.25},
  191. },
  192. },
  193. groups = {cracky=2, atm = 1},
  194. on_place = function(itemstack, placer, pointed_thing)
  195. local under = pointed_thing.under
  196. local pos
  197. if minetest.registered_items[minetest.get_node(under).name].buildable_to then
  198. pos = under
  199. else
  200. pos = pointed_thing.above
  201. end
  202. if minetest.is_protected(pos, placer:get_player_name()) and
  203. not minetest.check_player_privs(placer, "protection_bypass") then
  204. minetest.record_protection_violation(pos, placer:get_player_name())
  205. return itemstack
  206. end
  207. local def = minetest.registered_nodes[minetest.get_node(pos).name]
  208. if not def or not def.buildable_to then
  209. minetest.remove_node(pos)
  210. return itemstack
  211. end
  212. local dir = minetest.dir_to_facedir(placer:get_look_dir())
  213. local pos2 = {x = pos.x, y = pos.y + 1, z = pos.z}
  214. local def2 = minetest.registered_nodes[minetest.get_node(pos2).name]
  215. if not def2 or not def2.buildable_to then
  216. return itemstack
  217. end
  218. minetest.set_node(pos, {name = "global_exchange:atm_bottom", param2 = dir})
  219. minetest.set_node(pos2, {name = "global_exchange:atm_top", param2 = dir})
  220. if not minetest.setting_getbool("creative_mode") then
  221. itemstack:take_item()
  222. return itemstack
  223. end
  224. end,
  225. can_dig = function(pos,player)
  226. local meta = minetest.get_meta(pos);
  227. local inv = meta:get_inventory()
  228. return inv:is_empty("main")
  229. end,
  230. on_destruct = function(pos)
  231. local pos2 = {x = pos.x, y = pos.y + 1, z = pos.z}
  232. local n2 = minetest.get_node(pos2)
  233. if minetest.get_item_group(n2.name, "atm") == 2 then
  234. minetest.remove_node(pos2)
  235. end
  236. end,
  237. on_construct = function(pos)
  238. local meta = minetest.get_meta(pos)
  239. meta:set_string("infotext", "ATM")
  240. local inv = meta:get_inventory()
  241. inv:set_size("main", 6)
  242. end,
  243. allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  244. local itname = stack:get_name()
  245. if coins_convert[itname] ~= nil then
  246. return stack:get_count()
  247. end
  248. return 0
  249. end,
  250. allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  251. return stack:get_count()
  252. end,
  253. on_metadata_inventory_put = function(pos, listname, index, stack, player)
  254. local itname = stack:get_name()
  255. if coins_convert[itname] ~= nil then
  256. local p_name = player:get_player_name()
  257. local meta = minetest.get_meta(pos)
  258. local inv = meta:get_inventory()
  259. local nb = stack:get_count()
  260. local amount = coins_convert[itname] * nb
  261. local succ, msg = exchange:give_credits(p_name, amount, S("Cash deposit (+@1)", amount))
  262. if succ then
  263. inv:set_stack(listname, index, nil)
  264. minetest.log("action", p_name.." put "..nb.." "..stack:get_name() .. " to ATM at " .. minetest.pos_to_string(pos))
  265. show_atm_form(withdraw_fs, p_name)
  266. --minetest.show_formspec(p_name, atm_form, deposit_fs(p_name))
  267. else
  268. minetest.log("error", p_name.." want to put "..nb.." "..stack:get_name().." to ATM at ".. minetest.pos_to_string(pos).." but: "..msg)
  269. end
  270. end
  271. end,
  272. on_metadata_inventory_take = function(pos, listname, index, stack, player)
  273. minetest.log("action", player:get_player_name().." take "..stack:get_count().." "..stack:get_name().." from ATM at "..minetest.pos_to_string(pos))
  274. end,
  275. on_rightclick = function(pos, _, clicker)
  276. local p_name = clicker:get_player_name()
  277. atm_pos[p_name] = pos
  278. minetest.sound_play("atm_beep", {pos = pos, gain = 0.3, max_hear_distance = 5})
  279. show_atm_form(main_menu_fs, clicker:get_player_name())
  280. end,
  281. })
  282. minetest.register_node("global_exchange:atm_top", {
  283. drawtype = "nodebox",
  284. tiles = {
  285. "global_exchange_atm_hi_top.png",
  286. "global_exchange_atm_side.png",--not visible anyway
  287. "global_exchange_atm_side.png",
  288. "global_exchange_atm_side.png",
  289. "global_exchange_atm_back.png",
  290. "global_exchange_atm_hi_front.png",
  291. },
  292. paramtype = "light",
  293. paramtype2 = "facedir",
  294. is_ground_content = false,
  295. light_source = 3,
  296. node_box = {
  297. type = "fixed",
  298. fixed = {
  299. {-0.500, -0.500, -0.5000, -0.375, 0.125, -0.25},
  300. { 0.375, -0.500, -0.5000, 0.500, 0.125, -0.25},
  301. {-0.500, -0.500, -0.2500, 0.500, 0.500, 0.50},
  302. {-0.500, 0.125, -0.4375, -0.375, 0.250, -0.25},
  303. { 0.375, 0.125, -0.4375, 0.500, 0.250, -0.25},
  304. {-0.500, 0.250, -0.3750, -0.375, 0.375, -0.25},
  305. { 0.375, 0.250, -0.3750, 0.500, 0.375, -0.25},
  306. {-0.500, 0.375, -0.3125, -0.375, 0.500, -0.25},
  307. { 0.375, 0.375, -0.3125, 0.500, 0.500, -0.25},
  308. }
  309. },
  310. selection_box = {
  311. type = "fixed",
  312. fixed = {0, 0, 0, 0, 0, 0},
  313. },
  314. groups = {
  315. atm = 2,
  316. not_in_creative_inventory = 1
  317. },
  318. })
  319. minetest.register_craft( {
  320. output = "global_exchange:atm",
  321. recipe = {
  322. { "default:stone", "default:stone", "default:stone" },
  323. { "default:stone", "default:gold_ingot", "default:stone" },
  324. { "default:stone", "default:stone", "default:stone" },
  325. }
  326. })
  327. minetest.register_alias("global_exchange:atm", "global_exchange:atm_bottom")
  328. -- vim:set ts=4 sw=4 noet: