BillBodkin

BodCoin ATM

Oct 16th, 2021 (edited)
332
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local selfName          = "computer_3"
  2. local ioChestName       = "minecraft:shulker_box_0"
  3. local vaultChestType    = "ironchest:obsidian_chest"
  4. local monitorName       = "monitor_0"
  5. local chatBoxName       = "chatBox_0"
  6. local plyrDetectorName  = "playerDetector_0"
  7. local modemName         = "modem_1"
  8.  
  9. local coinName = "minecraft:enchanted_golden_apple"
  10. local coinDisplayName = "BodCoin"
  11. local coinNbt = "a86eafd0148c1abb39f9b1e9e5d3cb70"
  12. local coinMaxStackSize = 64
  13. local itemDisplayNameScreen = "Enchanted Golden Apple"
  14. local bodCoinPerCoin = 100000
  15.  
  16. local bgColor = colors.blue
  17. local acColor = colors.white
  18. local textScale = 0.85
  19.  
  20. --------
  21.  
  22. local ioChest = peripheral.wrap(ioChestName)
  23. local vaultChests  = { peripheral.find(vaultChestType) }
  24. local monitor = peripheral.wrap(monitorName)
  25. monitor.setTextScale(textScale)
  26. local monWidth, monHeight = monitor.getSize()
  27.  
  28. rednet.open(modemName)
  29.  
  30. function Load(file)
  31.     local file = fs.open(file, "r")
  32.     if file == nil then
  33.         return {}
  34.     end
  35.     local toRet = textutils.unserialise(file.readAll())
  36.     file.close()
  37.     return toRet
  38. end
  39.  
  40. local bankAccounts = Load("bankAccounts")
  41. local passwords = Load("passwords")
  42. local pendingTransactions = Load("pendingTransactions")
  43. local completedTransactions = Load("completedTransactions")
  44.  
  45. function Save(file, data)
  46.     local file = fs.open(file, "w")
  47.     file.write(textutils.serialise(data))
  48.     file.close()
  49. end
  50.  
  51. function CommaValue(amount)
  52.   local formatted = amount
  53.   while true do  
  54.     formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
  55.     if (k==0) then
  56.       break
  57.     end
  58.   end
  59.   return formatted
  60. end
  61.  
  62. function RandomString(len)
  63.     --https://www.codegrepper.com/code-examples/lua/how+to+generate+a+random+string+in+lua
  64.     local res = ""
  65.     for i = 1, len do
  66.         res = res .. string.char(math.random(97, 122))
  67.     end
  68.     return res
  69. end
  70.  
  71. function Store(ioSlot, count)
  72.     local toMove = count
  73.     for chestName, chest in pairs(vaultChests) do
  74.         local chestItems = chest.list()
  75.         for slot = 1, chest.size() do
  76.             if chestItems[slot] == nil or chestItems[slot].count < coinMaxStackSize then
  77.                 toMove = toMove - vaultChests[chestName].pullItems(ioChestName, ioSlot, toMove, slot)
  78.                 if toMove == 0 then
  79.                     return
  80.                 end
  81.             end
  82.         end
  83.     end
  84.     error("Run out of storage in vault!")
  85. end
  86.  
  87. function Get(count)
  88.     local toMove = count
  89.     for chestName, chest in pairs(vaultChests) do
  90.         local chestItems = chest.list()
  91.         for slot = 1, chest.size() do
  92.             if chestItems[slot] ~= nil then
  93.                 toMove = toMove - vaultChests[chestName].pushItems(ioChestName, slot, toMove)
  94.                 if toMove == 0 then
  95.                     return
  96.                 end
  97.             end
  98.         end
  99.     end
  100.     error("Run out of items in vault!")
  101. end
  102.  
  103. function AcceptCoins(playerName)
  104.     local coinsAccepted = 0
  105.     for i = 1, ioChest.size() do
  106.         local item = ioChest.getItemDetail(i)
  107.         if item ~= nil and item.name == coinName and item.displayName == coinDisplayName and item.nbt == coinNbt then
  108.             Store(i, item.count)
  109.             coinsAccepted = coinsAccepted + item.count
  110.         end
  111.     end
  112.     print("Accepted " .. tostring(coinsAccepted) .. " items from " .. playerName)
  113.     AddBalance(playerName, coinsAccepted * bodCoinPerCoin)
  114.     return coinsAccepted
  115. end
  116.  
  117. function WithdrawCoins(playerName, amt)
  118.     local amtWithdrawn = 0
  119.     while amtWithdrawn < amt do
  120.         if AddBalance(playerName, -bodCoinPerCoin) == nil then
  121.             return amtWithdrawn
  122.         end
  123.         Get(1)
  124.         amtWithdrawn = amtWithdrawn + 1
  125.     end
  126.     return amtWithdrawn
  127. end
  128.  
  129. function GetBalance(username)
  130.     if bankAccounts[username] ~= nil then
  131.         return bankAccounts[username]
  132.     end
  133.     return 0
  134. end
  135.  
  136. function ValidateAmount(amount, mustBe)
  137.     if type(amount) ~= "number" then
  138.         return false, "Amount must be a number"
  139.     end
  140.     if mustBe == "neg" and amount >= 0 then
  141.         return false, "Amount must be negative"
  142.     end
  143.     if mustBe == "pos" and amount <= 0 then
  144.         return false, "Amount must be positive"
  145.     end
  146.     if amount ~= math.floor(amount) then
  147.         return false, "Amount must be integer"
  148.     end
  149.    
  150.     return true
  151. end
  152.  
  153. function AddBalance(username, change, mustBe)
  154.     local amountValid, notValidReason = ValidateAmount(change, mustBe)
  155.     if amountValid == false then
  156.         return nil, notValidReason
  157.     end
  158.    
  159.     if bankAccounts[username] == nil then
  160.         bankAccounts[username] = 0
  161.     end
  162.     if bankAccounts[username] + change < 0 then
  163.         return nil, "Insufficient funds"
  164.     end
  165.     bankAccounts[username] = bankAccounts[username] + change
  166.     Save("bankAccounts", bankAccounts)
  167.     return bankAccounts[username]
  168. end
  169.  
  170. function AddPendingTransaction(to, from, amount)
  171.     local amountValid, notValidReason = ValidateAmount(amount, "pos")
  172.     if amountValid == false then
  173.         return nil, notValidReason
  174.     end
  175.     if type(to) ~= "string" then
  176.         return nil, "'to' must be string"
  177.     end
  178.     if type(from) ~= "string" then
  179.         return nil, "'from' must be string"
  180.     end
  181.     local transactionID = RandomString(16)
  182.     pendingTransactions[transactionID] = {
  183.         ["to"] = to,
  184.         ["from"] = from,
  185.         ["amount"] = amount,
  186.         ["timestamp"] = os.date(),
  187.     }
  188.     Save("pendingTransactions", pendingTransactions)
  189.     return transactionID
  190. end
  191.  
  192. function CompletePendingTransaction(transactionID, allow)
  193.     if pendingTransactions[transactionID] == nil then
  194.         return false, "Transaction not found"
  195.     end
  196.    
  197.     if type(allow) ~= "boolean" then
  198.         return false, "'allow' is not boolean"
  199.     end
  200.    
  201.     completedTransactions[transactionID] = {
  202.         ["to"] = pendingTransactions[transactionID]["to"],
  203.         ["from"] = pendingTransactions[transactionID]["from"],
  204.         ["amount"] = pendingTransactions[transactionID]["amount"],
  205.         ["timestamp"] = pendingTransactions[transactionID]["timestamp"],
  206.         ["allowed"] = allow
  207.     }
  208.    
  209.     pendingTransactions[transactionID] = nil
  210. end
  211.  
  212. function GetTransactionStatus(transactionID)
  213.     if pendingTransactions[transactionID] ~= nil then
  214.         return "pending"
  215.     end
  216.     if completedTransactions[transactionID] ~= nil then
  217.         if completedTransactions[transactionID]["allowed"] then
  218.             return "allowed"
  219.         else
  220.             return "denied"
  221.         end
  222.     end
  223.     return "notFound"
  224. end
  225.  
  226. function GetPendingTo(to)
  227.     local toRet = {}
  228.     for k, v in pairs(pendingTransactions) do
  229.         if v.to == to then
  230.             table.insert(toRet, v)
  231.         end
  232.     end
  233.     return toRet
  234. end
  235.  
  236. function GetPendingFrom(to)
  237.     local toRet = {}
  238.     for k, v in pairs(pendingTransactions) do
  239.         if v.from == from then
  240.             table.insert(toRet, v)
  241.         end
  242.     end
  243.     return toRet
  244. end
  245.  
  246. function GetCompletedTo(to)
  247.     local toRet = {}
  248.     for k, v in pairs(completedTransactions) do
  249.         if v.to == to then
  250.             table.insert(toRet, v)
  251.         end
  252.     end
  253.     return toRet
  254. end
  255.  
  256. function GetCompletedFrom(to)
  257.     local toRet = {}
  258.     for k, v in pairs(completedTransactions) do
  259.         if v.from == from then
  260.             table.insert(toRet, v)
  261.         end
  262.     end
  263.     return toRet
  264. end
  265.  
  266. ----
  267.  
  268. function WriteCenter(txt, line)
  269.     monitor.setCursorPos(math.ceil((monWidth - string.len(txt)) / 2), line)
  270.     monitor.write(txt)
  271. end
  272.  
  273. function Button(txt, line)
  274.     monitor.setTextColor(bgColor)
  275.     monitor.setBackgroundColor(acColor)
  276.     monitor.setCursorPos(1, line)
  277.     monitor.clearLine()
  278.     WriteCenter(txt, line)
  279.     monitor.setTextColor(acColor)
  280.     monitor.setBackgroundColor(bgColor)
  281. end
  282.  
  283. function Session()
  284.     monitor.setTextColor(acColor)
  285.     monitor.setBackgroundColor(bgColor)
  286.     monitor.clear()
  287.     print("Waiting for player click to start session")
  288.     WriteCenter("Welcome", 2)
  289.     WriteCenter("Press the green", 7)
  290.     WriteCenter("block on the", 8)
  291.     WriteCenter("right to login.", 9)
  292.     local playerClickEvent, username = os.pullEvent("playerClick")
  293.     print("Starting session for " .. username)
  294.     function HomeScreen()
  295.         monitor.clear()
  296.         WriteCenter("Welcome", 2)
  297.         WriteCenter(username, 3)
  298.        
  299.         AddBalance(username, 0, true)--add to bankAccounts table
  300.  
  301.         WriteCenter("What would you like to do?", 7)
  302.        
  303.         Button("Check balance", 11)
  304.         Button("Deposit", 13)
  305.         Button("Withdraw", 15)
  306.         Button("Log out", 17)
  307.  
  308.         local nextScreen = nil
  309.         while nextScreen == nil do
  310.             local touchEvent, monitorTouched, x, y = os.pullEvent("monitor_touch")
  311.             if y == 11 then
  312.                 nextScreen = "checkBalance"
  313.             elseif y == 13 then
  314.                 nextScreen = "deposit"
  315.             elseif y == 15 then
  316.                 nextScreen = "withdraw"
  317.             elseif y == 17 then
  318.                 nextScreen = "logOut"
  319.             end
  320.         end
  321.        
  322.         return nextScreen
  323.     end
  324.    
  325.     function WriteExchangeRate(line)
  326.         WriteCenter("1 " .. itemDisplayNameScreen, line)
  327.         WriteCenter("is worth " .. CommaValue(bodCoinPerCoin) .. " BodCoins", line + 1)
  328.     end
  329.    
  330.     function BackButton()
  331.         Button("Back", 17)
  332.         while true do
  333.             local touchEvent, monitorTouched, x, y = os.pullEvent("monitor_touch")
  334.             if y == 17 then
  335.                 break
  336.             end
  337.         end
  338.     end
  339.    
  340.     while true do
  341.         local nextScreen = HomeScreen()
  342.         if nextScreen == "checkBalance" then
  343.             monitor.clear()
  344.             WriteCenter("Balance for", 2)
  345.             WriteCenter(username, 3)
  346.             WriteCenter(CommaValue(GetBalance(username)) .. " BodCoin", 5)
  347.             WriteExchangeRate(7)
  348.             BackButton()
  349.         elseif nextScreen == "deposit" then
  350.             function DepositScreen()
  351.                 while true do
  352.                     monitor.clear()
  353.                     WriteCenter("Please rename your", 2)
  354.                     WriteCenter(itemDisplayNameScreen .. "(s)", 3)
  355.                     WriteCenter("to '" .. coinDisplayName .. "' using the anvils.", 4)
  356.                    
  357.                     WriteCenter("Then insert them into the", 6)
  358.                     WriteCenter("shulker on the left.", 7)
  359.                    
  360.                     WriteCenter("Then, press 'Deposit'", 9)
  361.                     WriteCenter("and the items will be counted", 10)
  362.                    
  363.                     WriteExchangeRate(12)
  364.  
  365.                     Button("Deposit", 15)
  366.                     Button("Back", 17)
  367.                     while true do
  368.                         local touchEvent, monitorTouched, x, y = os.pullEvent("monitor_touch")
  369.                         if y == 15 then
  370.                             break
  371.                         elseif y == 17 then
  372.                             return
  373.                         end
  374.                     end
  375.                     monitor.clear()
  376.                     local itemsAdded = AcceptCoins(username)
  377.                     WriteCenter(tostring(itemsAdded) .. " " .. itemDisplayNameScreen .. "(s)", 4)
  378.                     WriteCenter("were accepted.", 5)
  379.                     WriteCenter("You now have: ", 8)
  380.                     WriteCenter(CommaValue(GetBalance(username)) .. " BodCoin", 9)
  381.                     BackButton()
  382.                 end
  383.             end
  384.             DepositScreen()
  385.         elseif nextScreen == "withdraw" then
  386.             function WithdrawScreen()
  387.                 while true do
  388.                     monitor.clear()
  389.                     Button("Withdraw 1 " .. itemDisplayNameScreen, 15)
  390.                     Button("Back", 17)
  391.                     while true do
  392.                         local touchEvent, monitorTouched, x, y = os.pullEvent("monitor_touch")
  393.                         if y == 15 then
  394.                             break
  395.                         elseif y == 17 then
  396.                             return
  397.                         end
  398.                     end
  399.                     monitor.clear()
  400.                     local withdrawn = WithdrawCoins(username, 1)
  401.                     WriteCenter(tostring(withdrawn) .. " " .. itemDisplayNameScreen .. "(s)", 4)
  402.                     WriteCenter("were withdrawn.", 5)
  403.                     WriteCenter("You now have: ", 8)
  404.                     WriteCenter(CommaValue(GetBalance(username)) .. " BodCoin", 9)
  405.                     BackButton()
  406.                 end
  407.             end
  408.             WithdrawScreen()
  409.         elseif nextScreen == "logOut" then
  410.             monitor.clear()
  411.             WriteCenter("Goodbye", 2)
  412.             sleep(3)
  413.             break
  414.         end
  415.     end
  416. end
  417.  
  418. function UI()
  419.     while true do
  420.         Session()
  421.     end
  422. end
  423.  
  424. function Network()
  425.     while true do
  426.         sleep(0)
  427.         function HandleIn()
  428.             local id, msg = rednet.receive("BodCoin")
  429.            
  430.             if type(msg) ~= "table" then
  431.                 rednet.send(id, {
  432.                     ["status"] = "fail",
  433.                     ["msg"] = "Data must be in table format"
  434.                 }, "BodCoinResp")
  435.                 return
  436.             end
  437.            
  438.             if type(msg["login"]) ~= "table" or msg["login"]["username"] == nil or passwords[msg["login"]["username"]] == nil or passwords[msg["login"]["username"]] ~= msg["login"]["password"] then
  439.                 rednet.send(id, {
  440.                     ["status"] = "fail",
  441.                     ["msg"] = "Invalid login"
  442.                 }, "BodCoinResp")
  443.                 return
  444.             end
  445.  
  446.             if msg["action"] == "getBalance" then
  447.                 rednet.send(id, {
  448.                     ["status"] = "success",
  449.                     ["balance"] = GetBalance(msg["login"]["username"])
  450.                 }, "BodCoinResp")
  451.                 return
  452.             end
  453.            
  454.             if msg["action"] == "checkAccountExists" then
  455.                 rednet.send(id, {
  456.                     ["status"] = "success",
  457.                     ["exists"] = bankAccounts[msg["username"]] ~= nil or passwords[msg["username"]] ~= nil
  458.                 }, "BodCoinResp")
  459.                 return
  460.             end
  461.            
  462.             if msg["action"] == "send" then
  463.                 if type(msg["to"]) ~= "string" then
  464.                     rednet.send(id, {
  465.                         ["status"] = "fail",
  466.                         ["msg"] = "'to' must be string"
  467.                     }, "BodCoinResp")
  468.                     return
  469.                 end
  470.                
  471.                 local amountValid, notValidReason = ValidateAmount(msg["amount"], "pos")
  472.                 if amountValid == false then
  473.                     rednet.send(id, {
  474.                         ["status"] = "fail",
  475.                         ["msg"] = notValidReason
  476.                     }, "BodCoinResp")
  477.                     return
  478.                 end
  479.                 local fromNewBal, fromBalChangeError = AddBalance(msg["login"]["username"], -msg["amount"], "neg")
  480.                 if fromNewBal == nil then
  481.                     rednet.send(id, {
  482.                         ["status"] = "fail",
  483.                         ["msg"] = fromBalChangeError
  484.                     }, "BodCoinResp")
  485.                     return
  486.                 end
  487.                
  488.                 local toNewBal, toBalChangeError = AddBalance(msg["to"], msg["amount"], "pos")
  489.                 if toNewBal == nil then
  490.                     error(toBalChangeError)
  491.                 end
  492.                
  493.                 rednet.send(id, {
  494.                     ["status"] = "success",
  495.                     ["exists"] = bankAccounts[msg["to"]] ~= nil or passwords[msg["to"]] ~= nil
  496.                 }, "BodCoinResp")
  497.                 return
  498.             end
  499.            
  500.             if msg["action"] == "request" then
  501.                 if type(msg["from"]) ~= "string" then
  502.                     rednet.send(id, {
  503.                         ["status"] = "fail",
  504.                         ["msg"] = "'from' must be string"
  505.                     }, "BodCoinResp")
  506.                     return
  507.                 end
  508.                
  509.                 local transactionID, failedReason = AddPendingTransaction(msg["login"]["username"], msg["from"], msg["amount"])
  510.                 if transactionID == nil then
  511.                     rednet.send(id, {
  512.                         ["status"] = "fail",
  513.                         ["msg"] = failedReason
  514.                     }, "BodCoinResp")
  515.                     return
  516.                 end
  517.                
  518.                 rednet.send(id, {
  519.                     ["status"] = "success",
  520.                     ["requestID"] = transactionID
  521.                 }, "BodCoinResp")
  522.                 return
  523.             end
  524.            
  525.             if msg["action"] == "getRequestStatus" then
  526.                 local transactionStatus, transactionStatusFailedMsg = GetTransactionStatus(msg["requestID"])
  527.                 if transactionStatus == nil then
  528.                     rednet.send(id, {
  529.                         ["status"] = "fail",
  530.                         ["msg"] = transactionStatusFailedMsg
  531.                     }, "BodCoinResp")
  532.                     return
  533.                 end
  534.                
  535.                 rednet.send(id, {
  536.                     ["status"] = "success",
  537.                     ["requestStatus"] = transactionStatus
  538.                 }, "BodCoinResp")
  539.                 return
  540.             end
  541.         end
  542.         HandleIn()
  543.     end
  544. end
  545.  
  546. parallel.waitForAny(UI, Network)
RAW Paste Data