From a2d21063747b69d8703ae645099f5d81a59f1983 Mon Sep 17 00:00:00 2001 From: Mazian Date: Wed, 5 Jul 2023 12:36:24 -0400 Subject: [PATCH] ATM TGUI Overhaul (#5666) --- code/modules/economy/Accounts.dm | 10 +- code/modules/economy/machines/ATM.dm | 610 +++++++++++--------------- tgui/packages/tgui/interfaces/ATM.tsx | 271 ++++++++++++ 3 files changed, 526 insertions(+), 365 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/ATM.tsx diff --git a/code/modules/economy/Accounts.dm b/code/modules/economy/Accounts.dm index 5034c5c3e1b4..e6257c87c59b 100644 --- a/code/modules/economy/Accounts.dm +++ b/code/modules/economy/Accounts.dm @@ -99,12 +99,10 @@ return 0 //this returns the first account datum that matches the supplied accnum/pin combination, it returns null if the combination did not match any account -/proc/attempt_account_access(var/attempt_account_number, var/attempt_pin_number, var/security_level_passed = 0) - for(var/datum/money_account/D in GLOB.all_money_accounts) - if(D.account_number == attempt_account_number) - if( D.security_level <= security_level_passed && (!D.security_level || D.remote_access_pin == attempt_pin_number) ) - return D - break +/proc/attempt_account_access(var/attempt_account_number, var/attempt_pin_number, var/valid_card) + var/datum/money_account/D = get_account(attempt_account_number) + if(D && (D.security_level != 2 || valid_card) && (!D.security_level || D.remote_access_pin == attempt_pin_number) ) + return D /proc/get_account(var/account_number) for(var/datum/money_account/D in GLOB.all_money_accounts) diff --git a/code/modules/economy/machines/ATM.dm b/code/modules/economy/machines/ATM.dm index 8a29d23068ad..4da4bb650bc3 100644 --- a/code/modules/economy/machines/ATM.dm +++ b/code/modules/economy/machines/ATM.dm @@ -7,10 +7,7 @@ log transactions */ -#define NO_SCREEN 0 -#define CHANGE_SECURITY_LEVEL 1 -#define TRANSFER_FUNDS 2 -#define VIEW_TRANSACTION_LOGS 3 +GLOBAL_LIST_INIT(atm_sounds, list('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg')) /obj/item/card/id/var/money = 2000 @@ -32,9 +29,8 @@ log transactions var/ticks_left_locked_down = 0 var/ticks_left_timeout = 0 var/machine_id = "" - var/obj/item/card/held_card + var/obj/item/card/id/held_card var/editing_security_level = 0 - var/view_screen = NO_SCREEN var/account_security_level = 0 var/datum/effect_system/spark_spread/spark_system @@ -60,11 +56,7 @@ log transactions for(var/obj/item/spacecash/S in src) S.loc = src.loc - if(prob(50)) - playsound(loc, 'sound/items/polaroid1.ogg', 50, 1) - else - playsound(loc, 'sound/items/polaroid2.ogg', 50, 1) - break + playsound(loc, pick(GLOB.atm_sounds), 50, 1) /obj/machinery/atm/emag_act(var/remaining_charges, var/mob/user) if(!emagged) @@ -128,376 +120,206 @@ log transactions else ..() -/obj/machinery/atm/attack_hand(mob/user, list/params) - if(istype(user, /mob/living/silicon)) - to_chat (user, SPAN_WARNING("A firewall prevents you from interfacing with this device!")) - return - if(get_dist(src,user) <= 1) - - //js replicated from obj/machinery/computer/card - var/dat = "

Automatic Teller Machine

" - dat += "For all your monetary needs!
" - dat += "This terminal is [machine_id]. Report this code when contacting IT Support
" - - if(emagged > 0) - dat += "Card: LOCKED

Unauthorized terminal access detected! This ATM has been locked. Please contact IT Support." - else - dat += "Card: [held_card ? held_card.name : "------"]

" +/obj/machinery/atm/proc/generate_ui_transaction_log(var/list/transaction_list) + var/list/passed_list = list() + for(var/datum/transaction/T in transaction_list) + var/transaction_num = 0 + var/list/new_list = list() + new_list["target_name"] = T.target_name + new_list["purpose"] = T.purpose + new_list["amount"] = T.amount + new_list["date"] = T.date + new_list["time"] = T.time + new_list["source_terminal"] = T.source_terminal + transaction_num++ + passed_list["[transaction_num]"] = new_list + return passed_list + +/obj/machinery/atm/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "ATM", "[machine_id]") + ui.open() - if(ticks_left_locked_down > 0) - dat += "Maximum number of pin attempts exceeded! Access to this ATM has been temporarily disabled." - else if(authenticated_account) - if(authenticated_account.suspended) - dat += "Access to this account has been suspended, and the funds within frozen." - else - switch(view_screen) - if(CHANGE_SECURITY_LEVEL) - dat += "Select a new security level for this account:

" - var/text = "Zero - Either the account number or card is required to access this account. EFTPOS transactions will require a card and ask for a pin, but not verify the pin is correct." - if(authenticated_account.security_level != 0) - text = "[text]" - dat += "[text]
" - text = "One - An account number and pin must be manually entered to access this account and process transactions." - if(authenticated_account.security_level != 1) - text = "[text]" - dat += "[text]
" - text = "Two - In addition to account number and pin, a card is required to access this account and process transactions." - if(authenticated_account.security_level != 2) - text = "[text]" - dat += "[text]

" - dat += "Back" - if(VIEW_TRANSACTION_LOGS) - dat += "Transaction logs
" - dat += "Back" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - for(var/datum/transaction/T in authenticated_account.transaction_log) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
DateTimeTargetPurposeValueSource terminal ID
[T.date][T.time][T.target_name][T.purpose]$[T.amount][T.source_terminal]
" - dat += "Print
" - if(TRANSFER_FUNDS) - dat += "Account balance: $[authenticated_account.money]
" - dat += "Back

" - dat += "
" - dat += "" - dat += "" - dat += "Target account number:
" - dat += "Funds to transfer:
" - dat += "Transaction purpose:
" - dat += "
" - dat += "
" - else - dat += "Welcome, [authenticated_account.owner_name].
" - dat += "Account balance: $[authenticated_account.money]" - dat += "
" - dat += "" - dat += " Cash Chargecard
" - dat += "" - dat += "
" - dat += "Change account security level
" - dat += "Make transfer
" - dat += "View transaction log
" - dat += "Print balance statement
" - dat += "Logout
" +/obj/machinery/atm/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state) + . = ..() + var/data[0] + + data["incorrect_attempts"] = number_incorrect_tries + data["max_pin_attempts"] = max_pin_attempts + data["ticks_left_locked_down"] = ticks_left_locked_down + data["emagged"] = emagged + data["authenticated_acc"] = (authenticated_account ? 1 : 0) + data["account_name"] = authenticated_account?.owner_name || "UNKWN" + data["transaction_log"] = generate_ui_transaction_log(authenticated_account?.transaction_log || list()) + data["account_security_level"] = account_security_level + data["current_account_security_level"] = authenticated_account?.security_level + data["acc_suspended"] = authenticated_account?.suspended || 0 + data["balance"] = authenticated_account?.money || 0 + data["machine_id"] = machine_id + data["card_inserted"] = (held_card ? TRUE : FALSE) + data["inserted_card_name"] = (held_card ? held_card.registered_name : "--INSERT CARD--") + data["logout_time"] = DisplayTimeText(ticks_left_timeout * 10) + + return data + +/obj/machinery/atm/ui_act(action, list/params, datum/tgui/ui) + . = ..() + var/mob/living/carbon/human/user = usr + switch(action) + if("attempt_authentication") + attempt_authentication(user, text2num(params["pin"]), text2num(params["acc"])) + if("eject_card") + if(held_card) + authenticated_account = null + account_security_level = 0 + release_held_id(user) else - if(!account_security_level) - dat += "To log in to your savings account, press 'submit' with ID clearly displayed. If you wish to log into another account, please enter the account number into the field below or insert a registered ID card into the slot above and then press 'submit'.
" - else if (account_security_level == 1) - dat += "This account requires a PIN to access. For security reasons the account # will need re-entered or ID bound to this account re-scanned." + //this might happen if the user had the browser window open when somebody emagged it + if(emagged > 0) + to_chat(user, "[icon2html(thing = src, target = user)] The ATM card reader rejected your ID because this machine has been sabotaged!") else - dat += "Due to the security settings on this account, all information needs to be re-entered and the ID bound to this account placed in the slot above.
" - dat += "
" - dat += "" - dat += "" - dat += "Account:
" - dat += "PIN:
" - dat += "
" - dat += "
" - - user << browse(dat,"window=atm;size=550x650") - else - user << browse(null,"window=atm") - -/obj/machinery/atm/Topic(var/href, var/href_list) - if(href_list["choice"]) - switch(href_list["choice"]) - if("transfer") - if(authenticated_account) - var/transfer_amount = text2num(href_list["funds_amount"]) - transfer_amount = round(transfer_amount, 0.01) - if(transfer_amount <= 0) - alert("That is not a valid amount.") - else if(transfer_amount <= authenticated_account.money) - var/target_account_number = text2num(href_list["target_acc_number"]) - var/transfer_purpose = href_list["purpose"] - if(charge_to_account(target_account_number, authenticated_account.owner_name, transfer_purpose, machine_id, transfer_amount)) - to_chat(usr, "[icon2html(thing = src, target = usr)]Funds transfer successful.") - authenticated_account.money -= transfer_amount - - //create an entry in the account transaction log - var/datum/transaction/T = new() - T.target_name = "Account #[target_account_number]" - T.purpose = transfer_purpose - T.source_terminal = machine_id - T.date = GLOB.current_date_string - T.time = stationtime2text() - T.amount = "([transfer_amount])" - authenticated_account.transaction_log.Add(T) - else - to_chat(usr, "[icon2html(thing = src, target = usr)]Funds transfer failed.") - - else - to_chat(usr, "[icon2html(thing = src, target = usr)]You don't have enough funds to do that!") - if("view_screen") - view_screen = text2num(href_list["view_screen"]) - if("change_security_level") - if(authenticated_account) - var/new_sec_level = max( min(text2num(href_list["new_security_level"]), 2), 0) - authenticated_account.security_level = new_sec_level - if("attempt_auth") - - var/obj/item/card/id/login_card - if(held_card) - login_card = held_card - else - login_card = scan_user(usr) - - if(!ticks_left_locked_down) - var/tried_account_num = text2num(href_list["account_num"]) - //We WILL need an account number entered manually if security is high enough, do not automagic account number - if(!tried_account_num && login_card && (account_security_level != 2)) - tried_account_num = login_card.associated_account_number - var/tried_pin = text2num(href_list["account_pin"]) - - //We'll need more information if an account's security is greater than zero so let's find out what the security setting is - var/datum/money_account/D - //Below is to avoid a runtime - if(tried_account_num) - D = get_account(tried_account_num) - - if(D) - account_security_level = D.security_level - - authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1) - if(!authenticated_account) - number_incorrect_tries++ - if(previous_account_number == tried_account_num) - if(number_incorrect_tries > max_pin_attempts) - //lock down the atm - ticks_left_locked_down = 30 - playsound(src, 'sound/machines/buzz-two.ogg', 50, 1) - - //create an entry in the account transaction log - var/datum/money_account/failed_account = get_account(tried_account_num) - if(failed_account) - var/datum/transaction/T = new() - T.target_name = failed_account.owner_name - T.purpose = "Unauthorised login attempt" - T.source_terminal = machine_id - T.date = GLOB.current_date_string - T.time = stationtime2text() - failed_account.transaction_log.Add(T) - else - to_chat(usr, "[icon2html(thing = src, target = usr)] Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempts remaining.") - previous_account_number = tried_account_num - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 1) - else - to_chat(usr, "[icon2html(thing = src, target = usr)] incorrect pin/account combination entered.") - number_incorrect_tries = 0 - else - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - ticks_left_timeout = 120 - view_screen = NO_SCREEN - - //create a transaction log entry - var/datum/transaction/T = new() - T.target_name = authenticated_account.owner_name - T.purpose = "Remote terminal access" - T.source_terminal = machine_id - T.date = GLOB.current_date_string - T.time = stationtime2text() - authenticated_account.transaction_log.Add(T) - - to_chat(usr, "[icon2html(thing = src, target = usr)] Access granted. Welcome user '[authenticated_account.owner_name].'") - - previous_account_number = tried_account_num - if("e_withdrawal") - var/amount = max(text2num(href_list["funds_amount"]),0) - amount = round(amount, 0.01) - if(amount <= 0) + var/obj/item/I = user.get_active_held_item() + if (istype(I, /obj/item/card/id)) + if(!user.attempt_insert_item_for_installation(I, src)) + return + held_card = I + if("balance_statement") + if(authenticated_account) + var/obj/item/paper/R = new(src.loc) + R.name = "Account balance: [authenticated_account.owner_name]" + R.info = "NT Automated Teller Account Statement

" + R.info += "Account holder: [authenticated_account.owner_name]
" + R.info += "Account number: [authenticated_account.account_number]
" + R.info += "Balance: $[authenticated_account.money]
" + R.info += "Date and time: [stationtime2text()], [GLOB.current_date_string]

" + R.info += "Service terminal ID: [machine_id]
" + + //stamp the paper + var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') + stampoverlay.icon_state = "paper_stamp-cent" + if(!R.stamped) + R.stamped = new + R.stamped += /obj/item/stamp + R.add_overlay(stampoverlay) + R.stamps += "
This paper has been stamped by the Automatic Teller Machine." + + playsound(loc, pick(GLOB.atm_sounds), 50, 1) + if("transfer") + if(authenticated_account) + var/transfer_amount = text2num(params["funds_amount"]) + transfer_amount = round(transfer_amount, 0.01) + if(transfer_amount <= 0) alert("That is not a valid amount.") - else if(authenticated_account && amount > 0) - if(amount <= authenticated_account.money) - playsound(src, 'sound/machines/chime.ogg', 50, 1) - - //remove the money - authenticated_account.money -= amount - - // spawn_money(amount,src.loc) - spawn_ewallet(amount,src.loc,usr) + else if(transfer_amount <= authenticated_account.money) + var/target_account_number = text2num(params["target_acc_number"]) + var/transfer_purpose = params["purpose"] + if(charge_to_account(target_account_number, authenticated_account.owner_name, transfer_purpose, machine_id, transfer_amount)) + to_chat(user, "[icon2html(thing = src, target = user)]Funds transfer successful.") + authenticated_account.money -= transfer_amount //create an entry in the account transaction log var/datum/transaction/T = new() - T.target_name = authenticated_account.owner_name - T.purpose = "Credit withdrawal" - T.amount = "([amount])" + T.target_name = "Account #[target_account_number]" + T.purpose = transfer_purpose T.source_terminal = machine_id T.date = GLOB.current_date_string T.time = stationtime2text() + T.amount = "([transfer_amount])" authenticated_account.transaction_log.Add(T) else - to_chat(usr, "[icon2html(thing = src, target = usr)]You don't have enough funds to do that!") - if("withdrawal") - var/amount = max(text2num(href_list["funds_amount"]),0) - amount = round(amount, 0.01) - if(amount <= 0) - alert("That is not a valid amount.") - else if(authenticated_account && amount > 0) - if(amount <= authenticated_account.money) - playsound(src, 'sound/machines/chime.ogg', 50, 1) - - //remove the money - authenticated_account.money -= amount - - spawn_money(amount,src.loc,usr) - - //create an entry in the account transaction log - var/datum/transaction/T = new() - T.target_name = authenticated_account.owner_name - T.purpose = "Credit withdrawal" - T.amount = "([amount])" - T.source_terminal = machine_id - T.date = GLOB.current_date_string - T.time = stationtime2text() - authenticated_account.transaction_log.Add(T) - else - to_chat(usr, "[icon2html(thing = src, target = usr)]You don't have enough funds to do that!") - if("balance_statement") - if(authenticated_account) - var/obj/item/paper/R = new(src.loc) - R.name = "Account balance: [authenticated_account.owner_name]" - R.info = "NT Automated Teller Account Statement

" - R.info += "Account holder: [authenticated_account.owner_name]
" - R.info += "Account number: [authenticated_account.account_number]
" - R.info += "Balance: $[authenticated_account.money]
" - R.info += "Date and time: [stationtime2text()], [GLOB.current_date_string]

" - R.info += "Service terminal ID: [machine_id]
" - - //stamp the paper - var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') - stampoverlay.icon_state = "paper_stamp-cent" - if(!R.stamped) - R.stamped = new - R.stamped += /obj/item/stamp - R.add_overlay(stampoverlay) - R.stamps += "
This paper has been stamped by the Automatic Teller Machine." - - if(prob(50)) - playsound(loc, 'sound/items/polaroid1.ogg', 50, 1) + to_chat(user, "[icon2html(thing = src, target = user)]Funds transfer failed.") else - playsound(loc, 'sound/items/polaroid2.ogg', 50, 1) - if ("print_transaction") - if(authenticated_account) - var/obj/item/paper/R = new(src.loc) - R.name = "Transaction logs: [authenticated_account.owner_name]" - R.info = "Transaction logs
" - R.info += "Account holder: [authenticated_account.owner_name]
" - R.info += "Account number: [authenticated_account.account_number]
" - R.info += "Date and time: [stationtime2text()], [GLOB.current_date_string]

" - R.info += "Service terminal ID: [machine_id]
" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - for(var/datum/transaction/T in authenticated_account.transaction_log) - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "" - R.info += "
DateTimeTargetPurposeValueSource terminal ID
[T.date][T.time][T.target_name][T.purpose]$[T.amount][T.source_terminal]
" - - //stamp the paper - var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') - stampoverlay.icon_state = "paper_stamp-cent" - if(!R.stamped) - R.stamped = new - R.stamped += /obj/item/stamp - R.add_overlay(stampoverlay) - R.stamps += "
This paper has been stamped by the Automatic Teller Machine." - - if(prob(50)) - playsound(loc, 'sound/items/polaroid1.ogg', 50, 1) - else - playsound(loc, 'sound/items/polaroid2.ogg', 50, 1) - - if("insert_card") - if(!held_card) - //this might happen if the user had the browser window open when somebody emagged it - if(emagged > 0) - to_chat(usr, "[icon2html(thing = src, target = usr)] The ATM card reader rejected your ID because this machine has been sabotaged!") + to_chat(user, "[icon2html(thing = src, target = user)]You don't have enough funds to do that!") + if("change_security_level") + if(authenticated_account) + var/new_sec_level = max( min(text2num(params["new_security_level"]), 2), 0) + authenticated_account.security_level = new_sec_level + if("withdrawal") + var/amount = max(text2num(params["funds_amount"]),0) + amount = round(amount, 0.01) + if(amount <= 0) + alert("That is not a valid amount.") + else if(authenticated_account && amount > 0) + if(amount <= authenticated_account.money) + playsound(src, 'sound/machines/chime.ogg', 50, 1) + + //remove the money + authenticated_account.money -= amount + + if(text2num(params["form_ewallet"])) + spawn_ewallet(amount,src.loc,user) else - var/obj/item/I = usr.get_active_held_item() - if (istype(I, /obj/item/card/id)) - if(!usr.attempt_insert_item_for_installation(I, src)) - return - held_card = I - else - release_held_id(usr) - if("logout") - authenticated_account = null - account_security_level = 0 - //usr << browse(null,"window=atm") + spawn_money(amount,src.loc,user) - src.attack_hand(usr) - -//stolen wholesale and then edited a bit from newscasters, which are awesome and by Agouri -/obj/machinery/atm/proc/scan_user(mob/living/carbon/human/human_user as mob) - if(!authenticated_account) - if(human_user.wear_id) - var/obj/item/card/id/I - if(istype(human_user.wear_id, /obj/item/card/id) ) - I = human_user.wear_id - else if(istype(human_user.wear_id, /obj/item/pda) ) - var/obj/item/pda/P = human_user.wear_id - I = P.id - if(I) - authenticated_account = attempt_account_access(I.associated_account_number) - if(authenticated_account) - to_chat(human_user, "[icon2html(thing = src, target = human_user)] Access granted. Welcome user '[authenticated_account.owner_name].'") - - //create a transaction log entry + //create an entry in the account transaction log var/datum/transaction/T = new() T.target_name = authenticated_account.owner_name - T.purpose = "Remote terminal access" + T.purpose = "Credit withdrawal" + T.amount = "([amount])" T.source_terminal = machine_id T.date = GLOB.current_date_string T.time = stationtime2text() authenticated_account.transaction_log.Add(T) + else + to_chat(user, "[icon2html(thing = src, target = user)]You don't have enough funds to do that!") + + if ("print_transaction") + if(authenticated_account) + var/obj/item/paper/R = new(src.loc) + R.name = "Transaction logs: [authenticated_account.owner_name]" + R.info = "Transaction logs
" + R.info += "Account holder: [authenticated_account.owner_name]
" + R.info += "Account number: [authenticated_account.account_number]
" + R.info += "Date and time: [stationtime2text()], [GLOB.current_date_string]

" + R.info += "Service terminal ID: [machine_id]
" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + for(var/datum/transaction/T in authenticated_account.transaction_log) + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "" + R.info += "
DateTimeTargetPurposeValueSource terminal ID
[T.date][T.time][T.target_name][T.purpose]$[T.amount][T.source_terminal]
" + + //stamp the paper + var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') + stampoverlay.icon_state = "paper_stamp-cent" + if(!R.stamped) + R.stamped = new + R.stamped += /obj/item/stamp + R.add_overlay(stampoverlay) + R.stamps += "
This paper has been stamped by the Automatic Teller Machine." + + playsound(loc, pick(GLOB.atm_sounds), 50, 1) + if("logout") + authenticated_account = null + account_security_level = 0 + +/obj/machinery/atm/attack_hand(mob/user, list/params) + if(istype(user, /mob/living/silicon)) + to_chat (user, SPAN_WARNING("A firewall prevents you from interfacing with this device!")) + return + ui_interact(user) - view_screen = NO_SCREEN +//stolen wholesale and then edited a bit from newscasters, which are awesome and by Agouri +/obj/machinery/atm/proc/scan_user(mob/living/carbon/human/human_user as mob) + if(!authenticated_account) + var/obj/item/card/id/I = human_user.GetIdCard() + if(istype(I)) + return I // put the currently held id on the ground or in the hand of the user /obj/machinery/atm/proc/release_held_id(mob/living/carbon/human/human_user as mob) @@ -519,3 +341,73 @@ log transactions human_user.put_in_hands(E) E.worth = sum E.owner_name = authenticated_account.owner_name + +/obj/machinery/atm/proc/attempt_authentication(var/mob/user, var/input_pin, var/input_acc) + var/obj/item/card/id/login_card + if(held_card) + login_card = held_card + else + login_card = scan_user(user) + + if(!ticks_left_locked_down) + var/tried_account_num = input_acc + //We WILL need an account number entered manually if security is high enough, do not automagic account number + if(!tried_account_num && login_card && (account_security_level != 2)) + tried_account_num = login_card.associated_account_number + var/tried_pin = input_pin + + //We'll need more information if an account's security is greater than zero so let's find out what the security setting is + var/datum/money_account/D + //Below is to avoid a runtime + if(tried_account_num) + D = get_account(tried_account_num) + if(D) + to_chat(user, "remote acc [D.account_number] remote pin [D.remote_access_pin]") + + if(D) + account_security_level = D.security_level + to_chat(user, "acc in [tried_account_num] pin [tried_pin]") + to_chat(user, "get acc [get_account(tried_account_num)]") + to_chat(user, "attempt accesss [attempt_account_access(tried_account_num, tried_pin, (login_card?.associated_account_number == tried_account_num))]") + authenticated_account = attempt_account_access(tried_account_num, tried_pin, (login_card?.associated_account_number == tried_account_num)) + if(!authenticated_account) + number_incorrect_tries++ + if(previous_account_number == tried_account_num) + if(number_incorrect_tries > max_pin_attempts) + //lock down the atm + ticks_left_locked_down = 30 + playsound(src, 'sound/machines/buzz-two.ogg', 50, 1) + + //create an entry in the account transaction log + var/datum/money_account/failed_account = get_account(tried_account_num) + if(failed_account) + var/datum/transaction/T = new() + T.target_name = failed_account.owner_name + T.purpose = "Unauthorised login attempt" + T.source_terminal = machine_id + T.date = GLOB.current_date_string + T.time = stationtime2text() + failed_account.transaction_log.Add(T) + else + to_chat(user, "[icon2html(thing = src, target = user)] Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempts remaining.") + previous_account_number = tried_account_num + playsound(user, 'sound/machines/buzz-sigh.ogg', 50, 1) + else + to_chat(usr, "[icon2html(thing = src, target = user)] Unable to log in to account, additional information may be required.") + number_incorrect_tries = 0 + else + playsound(user, 'sound/machines/twobeep.ogg', 50, 1) + ticks_left_timeout = 120 + + //create a transaction log entry + var/datum/transaction/T = new() + T.target_name = authenticated_account.owner_name + T.purpose = "Remote terminal access" + T.source_terminal = machine_id + T.date = GLOB.current_date_string + T.time = stationtime2text() + authenticated_account.transaction_log.Add(T) + + to_chat(user, "[icon2html(thing = src, target = user)] Access granted. Welcome user '[authenticated_account.owner_name].'") + + previous_account_number = tried_account_num diff --git a/tgui/packages/tgui/interfaces/ATM.tsx b/tgui/packages/tgui/interfaces/ATM.tsx new file mode 100644 index 000000000000..7b0333db8582 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ATM.tsx @@ -0,0 +1,271 @@ +import { Section, Flex, Box, Button, Input, LabeledList, Collapsible, Divider } from "../components"; +import { Window } from "../layouts"; +import { useBackend, useLocalState } from "../backend"; + +const ACCOUNT_SECURITY_DESCRIPTIONS: AccountSecurityDescription[] = [{ "level": 0, "desc": "Only account number required, automatically scanned from ID in proximity." }, + { "level": 1, "desc": "Account number and PIN required; ID autoscan disabled." }, + { "level": 2, "desc": "Inserted ID card, Account number, and PIN required." }]; + +enum AccountSecurityLevels { + SECURITY_LEVEL_MIN = 0, + SECURITY_LEVEL_MED = 1, + SECURITY_LEVEL_MAX = 2} + +interface AccountSecurityDescription { + level: number, + desc: String +} + +type AccountTransactionLog = Record; + +interface AccountTransactionEntry { + target_name: string; + purpose: string; + amount: number; + date: string; + time: string; + source_terminal: string; +} + +interface ATMContext { + "incorrect_attempts" : number, + "max_pin_attempts" : number, + "ticks_left_locked_down": number, + "emagged" : boolean, + "authenticated_acc" : boolean, + "account_name" : String, + "transaction_log": AccountTransactionLog, + "account_security_level" : number, + "current_account_security_level" : number, + "acc_suspended": boolean, + "balance": number, + "machine_id": String + "card_inserted": boolean, + "inserted_card_name": String, + "logout_time": String, +} + +export const ATM = (props, context) => { + const { act, data } = useBackend(context); + if (!data.authenticated_acc) { + return ( + + +
+ {data.ticks_left_locked_down || data.emagged ? () : () } +
+
+
+ ); } + return ( + + +
+ +
+
+
+ ); +}; + +const LoginElement = (props, context) => { + const { act, data } = useBackend(context); + const [epin, setPin] = useLocalState( + context, + "epin", + 0 + ); + const [eacc, setAcc] = useLocalState( + context, + "eacc", + 0 + ); + return ( + + + Welcome to this Nanotrasen Automatic Teller Machine.
+ Please authenticate yourself by inserting your ID card or
+ entering your account number/PIN to continue.

+ + +
+
+ + setAcc(value)} textAlign="right" />
+
+ + setPin(value)} textAlign="right" />

+
+
+
+
+
+ ); + +}; + +const LockedElement = (props, context) => { + const { act, data } = useBackend(context); + return ( + +
+ + [ERR 1S - TERMINAL LOCKED OUT] + +

+ + + Welcome to this Nanotrasen Automatic Teller Machine. + Unfortunately, we are unable to service your requests at this time. + This terminal has been taken out of service due to a security incident. + Please contact the Command department for more information. + +
+ + + [ERR 1S - TERMINAL LOCKED OUT] + +
+ +
+ ); +}; + +const ATMElement = (props, context) => { + const { act, data } = useBackend(context); + const [TransferTarget, setTransferTarget] = useLocalState(context, "TransferTarget", 1); + const [TransferAmount, setTransferAmount] = useLocalState(context, "TransferAmount", 1); + const [TransferPurpose, setTransferPurpose] = useLocalState(context, "TransferPurpose", ""); + const [WithdrawAmount, setWithdrawAmount] = useLocalState(context, "WithdrawAmount", 1); + const [EWallet, setEWallet] = useLocalState(context, "EWallet", false); + const [Security, setSecurity] = useLocalState(context, "Security", data.current_account_security_level); + + if (data.acc_suspended) { + return ( + +
+ + [ERR 5Ac - ACCOUNT SUSPENDED] + +

+ + + Welcome to this Nanotrasen Automatic Teller Machine. + Unfortunately, we are unable to service your requests at this time. + This account has been suspended by the authority of the authorized feduciary aboard this facility. + Please contact the Command department for more information. + +
+ + + [ERR 5Ac - ACCOUNT SUSPENDED] + +
+ +
+ ); + } + + return ( + + + Welcome, {data.account_name}.
+ Current Funds: {data.balance} cr
+ Inserted ID:
+ For your security, you will be logged out in {data.logout_time}.
+
+ + + + + + { setSecurity(AccountSecurityLevels.SECURITY_LEVEL_MIN); act('change_security_level', { new_security_level: Security }); }} >Minimal Security + + { setSecurity(AccountSecurityLevels.SECURITY_LEVEL_MED); act('change_security_level', { new_security_level: Security }); }} >Medium Security + + { setSecurity(AccountSecurityLevels.SECURITY_LEVEL_MAX); act('change_security_level', { new_security_level: Security }); }} >Maximum Security + + + + {ACCOUNT_SECURITY_DESCRIPTIONS.find(option => option.level === Security)?.desc} + + + + + + + { + Object.entries(data.transaction_log).map(([numString, logEntry]) => { + return ( + + + + + {logEntry.target_name} + + + {logEntry.purpose} + + + {logEntry.amount}cr + + + {logEntry.date} + + + {logEntry.time} + + + {logEntry.source_terminal} + + + + + ); + }) + } + + + + + + + + setTransferTarget(value)} /> + + + setTransferPurpose(value)} /> + + + setTransferAmount(value)} /> + + + + + + + + + setWithdrawAmount(value)} /> + + + setEWallet(!EWallet)} >EWallet + + + + + + + + + + +
+ ); +};