Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Turtle ATM Script (turtle_atm.lua) with Gold Standard Validation
- -- Secure Transaction Logic: Acquire -> Verify -> Server -> Commit/Revert
- local modem = peripheral.find("modem")
- if not modem then error("No modem found") end
- modem.open(1) -- Open channel 1 for communication with server
- modem.open(2) -- Open channel 2 for communication with ATM terminal
- local function debug(message)
- print(os.date("%H:%M:%S") .. ": " .. message)
- end
- local function sendToServer(message)
- modem.transmit(1, 1, message)
- local timer = os.startTimer(5)
- while true do
- local event, id, channel, replyChannel, response = os.pullEvent()
- if event == "modem_message" and channel == 1 then
- return response
- elseif event == "timer" and id == timer then
- return nil
- end
- end
- end
- local function sendToATM(message)
- modem.transmit(2, 2, message)
- end
- local function findBlock(blockType)
- for i = 1, 4 do
- local success, data = turtle.inspect()
- if success and data.name:find(blockType) then
- return true
- end
- turtle.turnRight()
- end
- return false
- end
- local function isGold(item)
- return item and item.name == "minecraft:gold_ingot"
- end
- -- Helper to dump specific item type to front
- local function dumpItems(itemCheckFn)
- for i = 1, 16 do
- local item = turtle.getItemDetail(i)
- if item and itemCheckFn(item) then
- turtle.select(i)
- turtle.drop()
- end
- end
- turtle.select(1)
- end
- -- Securely acquire X gold from front inventory (Barrel or Chest)
- -- Keeps gold in turtle inventory. Returns success, message.
- -- If targetAmount is "max", grabs everything valid.
- local function acquireGold(targetAmount)
- local acquired = 0
- local isMax = (targetAmount == "max")
- while true do
- local needed = 64
- if not isMax then
- needed = targetAmount - acquired
- if needed > 64 then needed = 64 end
- if needed <= 0 then break end
- end
- -- Try to suck needed amount
- if turtle.suck(needed) then
- -- Verify what we just sucked
- local foundInvalid = false
- for i = 1, 16 do
- local item = turtle.getItemDetail(i)
- if item and not isGold(item) then
- -- Junk item, spit it back
- turtle.select(i)
- turtle.drop()
- turtle.select(1)
- foundInvalid = true
- end
- end
- if foundInvalid and not isMax then
- -- If we found invalid items during a specific request, we might want to fail or just retry?
- -- simple retry logic: just continue loop, we counted valid ones below
- end
- -- Recount total gold in inventory to update progress
- local currentTotal = 0
- for i = 1, 16 do
- local item = turtle.getItemDetail(i)
- if item and isGold(item) then
- currentTotal = currentTotal + item.count
- end
- end
- -- If we haven't increased, break (empty source)
- if currentTotal <= acquired then
- break
- end
- acquired = currentTotal
- else
- break -- Sucking failed (empty)
- end
- end
- if isMax then
- if acquired > 0 then
- return true, acquired
- else
- return false, "No Gold Found"
- end
- else
- if acquired >= targetAmount then
- return true, acquired
- else
- return false, "Insufficient Gold: Found " .. acquired .. " of " .. targetAmount
- end
- end
- end
- -- Dumps ALL gold in inventory to front
- local function commitGold()
- dumpItems(isGold)
- end
- local function handleDeposit(studentId, password, amount)
- local isMax = (amount == "max")
- local targetGold = 0
- if not isMax then
- targetGold = amount * 16
- end
- if isMax then
- debug("Deposit: MAX Eddies")
- else
- debug("Deposit: " .. amount .. " Eddies (" .. targetGold .. " Gold)")
- end
- if not findBlock("barrel") then
- sendToATM({action="complete", success=false, error="Barrel not found"})
- return
- end
- -- 1. Acquire from Barrel
- local success, result = acquireGold(isMax and "max" or targetGold)
- if not success then
- sendToATM({action="complete", success=false, error=result or "Failed to collect gold"})
- -- Refund what we grabbed back to Barrel
- commitGold()
- return
- end
- local totalGold = result
- local eddies = math.floor(totalGold / 16)
- local remainder = totalGold % 16
- if eddies <= 0 then
- sendToATM({action="complete", success=false, error="Not enough gold for 1 Eddie (Need 16)"})
- commitGold()
- return
- end
- -- 2. Server Update
- local response = sendToServer({type="update_balance", studentId=studentId, password=password, action="deposit", amount=eddies})
- if response and response.type == "balance_update_response" and response.success then
- -- 3. Success: Move processed gold to Chest, remainder to Barrel
- -- We need to separate the gold.
- -- Drop 'remainder' to Barrel first
- if remainder > 0 then
- -- We need to drop exactly 'remainder' amount.
- -- This is tricky because we have stacks.
- -- Logic: Select stacks, drop until remainder is gone.
- local dropped = 0
- for i = 1, 16 do
- local item = turtle.getItemDetail(i)
- if item and isGold(item) then
- local toDrop = math.min(remainder - dropped, item.count)
- if toDrop > 0 then
- turtle.select(i)
- turtle.drop(toDrop)
- dropped = dropped + toDrop
- end
- if dropped >= remainder then break end
- end
- end
- turtle.select(1)
- end
- -- Now find chest and dump the rest (which should be exactly eddies * 16)
- if findBlock("chest") then
- commitGold() -- Dump valid gold to chest
- sendToATM({action="complete", success=true, newBalance=response.result, amountDeposited=eddies, remainder=remainder})
- else
- -- Critical Error: Chest missing after transaction!
- debug("CRITICAL: Chest missing during deposit commit!")
- -- Attempt to return to barrel to be safe
- if findBlock("barrel") then
- commitGold()
- sendToATM({action="complete", success=true, newBalance=response.result, warning="System Error: Chest full/missing. Gold returned to barrel but balance updated. Contact Admin.", amountDeposited=eddies, remainder=remainder})
- else
- sendToATM({action="complete", success=true, newBalance=response.result, warning="System Error: Gold stuck in Turtle.", amountDeposited=eddies, remainder=remainder})
- end
- end
- else
- -- 4. Fail: Refund to Barrel
- debug("Server deposit failed: " .. (response and response.message or "Timeout"))
- if findBlock("barrel") then
- commitGold() -- Dump back to barrel
- end
- sendToATM({action="complete", success=false, error="Server rejected deposit"})
- end
- end
- local function handleWithdraw(studentId, password, amount)
- local goldAmount = amount * 16
- debug("Withdraw: " .. amount .. " Eddies (" .. goldAmount .. " Gold)")
- -- 0. Check Balance First (Quick fail)
- local balanceResponse = sendToServer({type="get_balance", studentId=studentId, password=password})
- if not (balanceResponse and balanceResponse.type == "balance_response") then
- sendToATM({action="complete", success=false, error="Failed to retrieve balance"})
- return
- end
- if tonumber(balanceResponse.balance) < amount then
- sendToATM({action="complete", success=false, error="Insufficient balance"})
- return
- end
- if not findBlock("chest") then
- sendToATM({action="complete", success=false, error="Chest not found"})
- return
- end
- -- 1. Acquire from Chest
- local success, msg = acquireGold(goldAmount)
- if not success then
- sendToATM({action="complete", success=false, error=msg or "ATM empty/low funds"})
- -- Revert to Chest
- commitGold()
- return
- end
- -- 2. Server Update
- local response = sendToServer({type="update_balance", studentId=studentId, password=password, action="withdraw", amount=amount})
- if response and response.type == "balance_update_response" and response.success then
- -- 3. Success: Move to Barrel
- if findBlock("barrel") then
- commitGold() -- Dump to barrel
- sendToATM({action="complete", success=true, newBalance=response.result})
- else
- debug("CRITICAL: Barrel missing during withdraw commit!")
- -- Refund to Chest? But user already paid on server.
- -- This is a 'money lost' scenario for user if we refund.
- -- Better to keep trying or dump?
- -- In production, we'd log this. Here, we return to Chest to save the gold and error out.
- -- User loses balance but ATM keeps gold. Admin intervention needed.
- commitGold()
- sendToATM({action="complete", success=false, error="Hardware error: Barrel missing. Gold retained in ATM. Contact Admin."})
- end
- else
- -- 4. Fail: Revert to Chest
- commitGold()
- sendToATM({action="complete", success=false, error="Server rejected withdraw"})
- end
- end
- while true do
- local event, side, channel, replyChannel, message = os.pullEvent("modem_message")
- if channel == 2 then
- if message.action == "deposit" then
- handleDeposit(message.studentId, message.password, message.amount)
- elseif message.action == "withdraw" then
- handleWithdraw(message.studentId, message.password, message.amount)
- end
- end
- end
Add Comment
Please, Sign In to add comment