--[[ m-banking — client/atm.lua ATM props: card check → PIN entry (ox_lib input) → open the same NUI in "atm" mode, which unlocks the card management tab and tightens the limits. ]] local ESX = exports.es_extended:getSharedObject() local atmTextUiOpen = false --- Ask for a PIN through ox_lib and hand it to the server for verification. --- Loops until the account unlocks the ATM, the player cancels, or the account --- gets locked out. local function requestPin() while true do local input = lib.inputDialog(L('card.enter_pin'), { { type = 'input', label = L('card.enter_pin'), description = L('card.pin_placeholder'), password = true, required = true, min = Config.PinLength, max = Config.PinLength } }) if not input or not input[1] then return end local pin = tostring(input[1]) if not pin:match('^%d+$') or #pin ~= Config.PinLength then Bank.Notify(nil, L('card.pin_format', Config.PinLength), 'error') Bank.PlaySound('error') else local result = lib.callback.await('m-banking:server:atmVerify', false, pin) if type(result) ~= 'table' then return end if result.status == 'ok' then Banking.Open('atm', result.data) return end Bank.Notify(nil, result.message or L('card.pin_wrong', 0), 'error') Bank.PlaySound('error') -- Locked out or something structurally wrong: stop asking. if result.status ~= 'wrong' then return end end end end --- Entry point for any ATM interaction. function UseATM() local result = lib.callback.await('m-banking:server:atmBegin', false) if type(result) ~= 'table' then return end if result.status ~= 'pin' then Bank.Notify(nil, result.message or L('notify.bank_closed'), 'error') Bank.PlaySound('error') return end Bank.PlaySound('beep') if not Bank.Progress(L('target.atm'), 1500) then return end requestPin() end -- ───────────────────────────────────────────────────────────────────────────── -- Interaction setup -- ───────────────────────────────────────────────────────────────────────────── CreateThread(function() while not ESX.IsPlayerLoaded() do Wait(200) end if Config.UseTarget then exports.ox_target:addModel(Config.ATMModels, { { name = 'm-banking:atm', icon = 'fa-solid fa-credit-card', label = L('target.atm'), distance = 1.5, onSelect = UseATM } }) return end -- Text UI fallback: scan for a nearby ATM prop. CreateThread(function() while true do local sleep = 1000 local ped = cache and cache.ped or PlayerPedId() local coords = GetEntityCoords(ped) local found = nil for _, model in ipairs(Config.ATMModels) do local object = GetClosestObjectOfType(coords.x, coords.y, coords.z, 1.5, model, false, false, false) if object ~= 0 then found = object break end end if found then sleep = 0 if not atmTextUiOpen then atmTextUiOpen = true lib.showTextUI(L('textui.atm'), { position = 'left-center' }) end if IsControlJustReleased(0, Config.InteractKey) then lib.hideTextUI() atmTextUiOpen = false UseATM() end elseif atmTextUiOpen then atmTextUiOpen = false lib.hideTextUI() end Wait(sleep) end end) end)