From 6d3e6c22460ef5805a9f8806703d28b602acd664 Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Tue, 3 Feb 2015 09:06:11 -0500 Subject: [PATCH 1/8] Change ewallets to work like ID cards Instead of having to insert ewallet before making purchase, ewallets can now be swiped in place of ID card --- code/game/machinery/vending.dm | 47 ++++++++++------------------------ 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 7d7ee3113d..205caf275f 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -59,8 +59,6 @@ var/datum/wires/vending/wires = null var/check_accounts = 0 // 1 = requires PIN and checks accounts. 0 = You slide an ID, it vends, SPACE COMMUNISM! - var/obj/item/weapon/spacecash/ewallet/ewallet - /obj/machinery/vending/New() ..() @@ -174,12 +172,17 @@ else if(istype(W, /obj/item/weapon/card) && currently_vending) var/obj/item/weapon/card/I = W scan_card(I) - else if (istype(W, /obj/item/weapon/spacecash/ewallet)) - user.drop_item() - W.loc = src - ewallet = W - user << "\blue You insert the [W] into the [src]" - + else if (istype(W, /obj/item/weapon/spacecash/ewallet) && currently_vending) + var/obj/item/weapon/spacecash/ewallet/I = W + if(I.worth >= currently_vending.price) + I.worth -= currently_vending.price + visible_message("[usr] swipes a card through [src].") + + src.vend(src.currently_vending, usr) + currently_vending = null + else + usr << "\icon[src]Your chargecard does not have enough money!" + else if(istype(W, /obj/item/weapon/wrench)) if(do_after(user, 20)) @@ -307,7 +310,7 @@ if(src.currently_vending) var/dat = "
[vendorname]


" //display the name, and added a horizontal rule - dat += "You have selected [currently_vending.product_name].
Please swipe your ID to pay for the article.

" + dat += "Product selected: [currently_vending.product_name]
Charge: [currently_vending.price]

Please swipe a card to pay for the item.
" dat += "Cancel" user << browse(dat, "window=vending") onclose(user, "") @@ -319,9 +322,6 @@ if (premium.len > 0) dat += "Coin slot: [coin ? coin : "No coin inserted"] (Remove)
" - if (ewallet) - dat += "Charge card's credits: [ewallet ? ewallet.worth : "No charge card inserted"] (Remove)

" - if (src.product_records.len == 0) dat += "No product loaded!" else @@ -378,16 +378,6 @@ usr << "\blue You remove the [coin] from the [src]" coin = null - if(href_list["remove_ewallet"] && !istype(usr,/mob/living/silicon)) - if (!ewallet) - usr << "There is no charge card in this machine." - return - ewallet.loc = src.loc - if(!usr.get_active_hand()) - usr.put_in_hands(ewallet) - usr << "\blue You remove the [ewallet] from the [src]" - ewallet = null - if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf)))) usr.set_machine(src) if ((href_list["vend"]) && (src.vend_ready) && (!currently_vending)) @@ -417,17 +407,8 @@ if(R.price == null) src.vend(R, usr) else - if (ewallet) - if (R.price <= ewallet.worth) - ewallet.worth -= R.price - src.vend(R, usr) - else - usr << "\red The ewallet doesn't have enough money to pay for that." - src.currently_vending = R - src.updateUsrDialog() - else - src.currently_vending = R - src.updateUsrDialog() + src.currently_vending = R + src.updateUsrDialog() return else if (href_list["cancel_buying"]) From 1acce5dc592fb4c61a5ff60d6430d4b1d89f1604 Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Tue, 3 Feb 2015 11:20:46 -0500 Subject: [PATCH 2/8] Add cash purchases to vending machines --- code/game/machinery/vending.dm | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 205caf275f..7698d2414c 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -182,7 +182,34 @@ currently_vending = null else usr << "\icon[src]Your chargecard does not have enough money!" - + + // we shouldn't hit spacewallets by now since they were handled above, so this is cash only + else if (istype(W, /obj/item/weapon/spacecash) && currently_vending) + var/obj/item/weapon/spacecash/C = W + if(C.worth >= currently_vending.price) + if(istype(C, /obj/item/weapon/spacecash/bundle)) + visible_message("[usr] inserts some cash into [src].") + C.worth -= currently_vending.price + if(!C.worth) + usr.drop_from_inventory(W) + del(W) + else + W.update_icon() + else + // Non-bundles are whole bills. We eat the whole cash item and spit out change + visible_message("[usr] inserts a bill into [src].") + var/left = C.worth - currently_vending.price + + usr.drop_from_inventory(W) + del(W) + if(left) + spawn_money(left, src.loc, user) + + src.vend(src.currently_vending, usr) + currently_vending = null + else + usr << "\icon[src]That is not enough money to cover the price!" + else if(istype(W, /obj/item/weapon/wrench)) if(do_after(user, 20)) @@ -310,7 +337,7 @@ if(src.currently_vending) var/dat = "
[vendorname]


" //display the name, and added a horizontal rule - dat += "Product selected: [currently_vending.product_name]
Charge: [currently_vending.price]

Please swipe a card to pay for the item.
" + dat += "Product selected: [currently_vending.product_name]
Charge: [currently_vending.price]

Please swipe a card or insert cash to pay for the item.
" dat += "Cancel" user << browse(dat, "window=vending") onclose(user, "") From 119005d515aba84c40735de085960e541d5ab8a1 Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Wed, 4 Feb 2015 19:26:47 -0500 Subject: [PATCH 3/8] Refactor and make vendors record transactions Payment code is now more modular and less redundant. Vending machines now record all transactions, including cash transactions. --- code/game/machinery/vending.dm | 249 ++++++++++++++++++++------------- 1 file changed, 148 insertions(+), 101 deletions(-) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 7698d2414c..5a8df08c8a 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -147,6 +147,29 @@ return /obj/machinery/vending/attackby(obj/item/weapon/W as obj, mob/user as mob) + if (currently_vending) + var/paid = 0 + var/handled = 0 + if(istype(W, /obj/item/weapon/card/id)) + var/obj/item/weapon/card/id/C = W + paid = pay_with_card(C) + handled = 1 + else if (istype(W, /obj/item/weapon/spacecash/ewallet)) + var/obj/item/weapon/spacecash/ewallet/C = W + paid = pay_with_ewallet(C) + handled = 1 + else if (istype(W, /obj/item/weapon/spacecash)) + var/obj/item/weapon/spacecash/C = W + paid = pay_with_cash(C, user) + handled = 1 + + if(paid) + src.vend(currently_vending, usr) + currently_vending = null + return + else if(handled) + return // don't smack that machine with your 2 thalers + if (istype(W, /obj/item/weapon/card/emag)) src.emagged = 1 user << "You short out the product lock on [src]" @@ -169,49 +192,7 @@ coin = W user << "\blue You insert the [W] into the [src]" return - else if(istype(W, /obj/item/weapon/card) && currently_vending) - var/obj/item/weapon/card/I = W - scan_card(I) - else if (istype(W, /obj/item/weapon/spacecash/ewallet) && currently_vending) - var/obj/item/weapon/spacecash/ewallet/I = W - if(I.worth >= currently_vending.price) - I.worth -= currently_vending.price - visible_message("[usr] swipes a card through [src].") - - src.vend(src.currently_vending, usr) - currently_vending = null - else - usr << "\icon[src]Your chargecard does not have enough money!" - - // we shouldn't hit spacewallets by now since they were handled above, so this is cash only - else if (istype(W, /obj/item/weapon/spacecash) && currently_vending) - var/obj/item/weapon/spacecash/C = W - if(C.worth >= currently_vending.price) - if(istype(C, /obj/item/weapon/spacecash/bundle)) - visible_message("[usr] inserts some cash into [src].") - C.worth -= currently_vending.price - if(!C.worth) - usr.drop_from_inventory(W) - del(W) - else - W.update_icon() - else - // Non-bundles are whole bills. We eat the whole cash item and spit out change - visible_message("[usr] inserts a bill into [src].") - var/left = C.worth - currently_vending.price - - usr.drop_from_inventory(W) - del(W) - if(left) - spawn_money(left, src.loc, user) - - src.vend(src.currently_vending, usr) - currently_vending = null - else - usr << "\icon[src]That is not enough money to cover the price!" - else if(istype(W, /obj/item/weapon/wrench)) - if(do_after(user, 20)) if(!src) return playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1) @@ -234,67 +215,133 @@ else ..() -/obj/machinery/vending/proc/scan_card(var/obj/item/weapon/card/I) - if(!currently_vending) return - if (istype(I, /obj/item/weapon/card/id)) - var/obj/item/weapon/card/id/C = I - visible_message("[usr] swipes a card through [src].") - var/datum/money_account/CH = get_account(C.associated_account_number) - if (CH) // Only proceed if card contains proper account number. - if(!CH.suspended) - if(CH.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2) - if(vendor_account) - var/attempt_pin = input("Enter pin code", "Vendor transaction") as num - var/datum/money_account/D = attempt_account_access(C.associated_account_number, attempt_pin, 2) - transfer_and_vend(D) - else - usr << "\icon[src]Unable to access account. Check security settings and try again." - else - //Just Vend it. - transfer_and_vend(CH) - else - usr << "\icon[src]Connected account has been suspended." +/** + * Receive payment with cashmoney. + * + * usr is the mob who gets the change. + */ +/obj/machinery/vending/proc/pay_with_cash(var/obj/item/weapon/spacecash/cashmoney, mob/user) + if(currently_vending.price > cashmoney.worth) + usr << "\icon[src] That is not enough money." + return 0 + + if(istype(cashmoney, /obj/item/weapon/spacecash/bundle)) + // Bundles can just have money subtracted, and will work + + visible_message("[usr] inserts some cash into [src].") + var/obj/item/weapon/spacecash/bundle/cashmoney_bundle = cashmoney + cashmoney_bundle.worth -= currently_vending.price + + if(cashmoney_bundle.worth <= 0) + usr.drop_from_inventory(cashmoney_bundle) + del(cashmoney_bundle) else - usr << "\icon[src]Error: Unable to access your account. Please contact technical support if problem persists." - -/obj/machinery/vending/proc/transfer_and_vend(var/datum/money_account/acc) - if(acc) - var/transaction_amount = currently_vending.price - if(transaction_amount <= acc.money) - - //transfer the money - acc.money -= transaction_amount - vendor_account.money += transaction_amount - - //create entries in the two account transaction logs - var/datum/transaction/T = new() - T.target_name = "[vendor_account.owner_name] (via [src.name])" - T.purpose = "Purchase of [currently_vending.product_name]" - if(transaction_amount > 0) - T.amount = "([transaction_amount])" - else - T.amount = "[transaction_amount]" - T.source_terminal = src.name - T.date = current_date_string - T.time = worldtime2text() - acc.transaction_log.Add(T) - // - T = new() - T.target_name = acc.owner_name - T.purpose = "Purchase of [currently_vending.product_name]" - T.amount = "[transaction_amount]" - T.source_terminal = src.name - T.date = current_date_string - T.time = worldtime2text() - vendor_account.transaction_log.Add(T) - - // Vend the item - src.vend(src.currently_vending, usr) - currently_vending = null - else - usr << "\icon[src]You don't have that much money!" + cashmoney_bundle.update_icon() else - usr << "\icon[src]Error: Unable to access your account. Please contact technical support if problem persists." + // Bills (banknotes) cannot really have worth different than face value, + // so we have to eat the bill and spit out change in a bundle + // This is really dirty, but there's no superclass for all bills, so we + // just assume that all spacecash that's not something else is a bill + + visible_message("[usr] inserts a bill into [src].") + var/left = cashmoney.worth - currently_vending.price + usr.drop_from_inventory(cashmoney) + del(cashmoney) + + if(left) + spawn_money(left, src.loc, user) + + // Vending machines have no idea who paid with cash + credit_purchase("(cash)") + return 1 + +/** + * Scan a chargecard and deduct payment from it. + * + * Takes payment for whatever is the currently_vending item. Returns 1 if + * successful, 0 if failed. + */ +/obj/machinery/vending/proc/pay_with_ewallet(var/obj/item/weapon/spacecash/ewallet/wallet) + visible_message("[usr] swipes a card through [src].") + if(currently_vending.price > wallet.worth) + usr << "\icon[src] Insufficient funds on chargecard." + return 0 + else + wallet.worth -= currently_vending.price + credit_purchase("[wallet.owner_name] (chargecard)") + return 1 + +/** + * Scan a card and attempt to transfer payment from associated account. + * + * Takes payment for whatever is the currently_vending item. Returns 1 if + * successful, 0 if failed + */ +/obj/machinery/vending/proc/pay_with_card(var/obj/item/weapon/card/id/I) + visible_message("[usr] swipes a card through [src].") + var/datum/money_account/customer_account = get_account(I.associated_account_number) + if (!customer_account) + usr << "\icon[src] Error: Unable to access account. Please contact technical support if problem persists." + return 0 + + if(customer_account.suspended) + usr << "\icon[src] Unable to access account: account suspended." + return 0 + + // Have the customer punch in the PIN before checking if there's enough money. Prevents people from figuring out acct is + // empty at high security levels + if(customer_account.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2) + var/attempt_pin = input("Enter pin code", "Vendor transaction") as num + customer_account = attempt_account_access(I.associated_account_number, attempt_pin, 2) + + if(!customer_account) + usr << "\icon[src] Unable to access account: incorrect credentials." + return 0 + + if(currently_vending.price > customer_account.money) + usr << "\icon[src] Insufficient funds in account." + return 0 + else + // Okay to move the money at this point + + // debit money from the purchaser's account + customer_account.money -= currently_vending.price + + // create entry in the purchaser's account log + var/datum/transaction/T = new() + T.target_name = "[vendor_account.owner_name] (via [src.name])" + T.purpose = "Purchase of [currently_vending.product_name]" + if(currently_vending.price > 0) + T.amount = "([currently_vending.price])" + else + T.amount = "[currently_vending.price]" + T.source_terminal = src.name + T.date = current_date_string + T.time = worldtime2text() + customer_account.transaction_log.Add(T) + + // Give the vendor the money. We use the account owner name, which means + // that purchases made with stolen/borrowed card will look like the card + // owner made them + credit_purchase(customer_account.owner_name) + return 1 + +/** + * Add money for current purchase to the vendor account. + * + * Called after the money has already been taken from the customer. + */ +/obj/machinery/vending/proc/credit_purchase(var/target as text) + vendor_account.money += currently_vending.price + + var/datum/transaction/T = new() + T.target_name = target + T.purpose = "Purchase of [currently_vending.product_name]" + T.amount = "[currently_vending.price]" + T.source_terminal = src.name + T.date = current_date_string + T.time = worldtime2text() + vendor_account.transaction_log.Add(T) /obj/machinery/vending/attack_ai(mob/user as mob) return attack_hand(user) From b826e5bb77d57de444a2d0fbe3531dd08d1832a0 Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Sat, 7 Feb 2015 09:55:43 -0500 Subject: [PATCH 4/8] Rewrite and refactor vending.dm Remove some redundant procs and /obj/machinery/vending members. Rework the way product lists and advert lists are stored within /obj/machinery/vending. /obj/machinery/vending now uses a simpler way of storing the product list. The old variables, `products`, `contraband`, etc., are still used for initialization of the new list, but are not used beyond that. This commit removes colors from vending lists in anticipation of introducing NanoUI. --- code/datums/wires/vending.dm | 8 +- code/game/machinery/vending.dm | 278 ++++++++++++++++----------------- 2 files changed, 141 insertions(+), 145 deletions(-) diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 91a95b47a7..67ca216251 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -1,3 +1,5 @@ +#define CAT_HIDDEN 2 // Also in code/game/machinery/vending.dm + /datum/wires/vending holder_type = /obj/machinery/vending wire_count = 4 @@ -27,7 +29,7 @@ var/const/VENDING_WIRE_IDSCAN = 8 . += ..() . += "
The orange light is [V.seconds_electrified ? "off" : "on"].
" . += "The red light is [V.shoot_inventory ? "off" : "blinking"].
" - . += "The green light is [V.extended_inventory ? "on" : "off"].
" + . += "The green light is [(V.categories & CAT_HIDDEN) ? "on" : "off"].
" . += "The [V.scan_id ? "purple" : "yellow"] light is on.
" /datum/wires/vending/UpdatePulsed(var/index) @@ -36,7 +38,7 @@ var/const/VENDING_WIRE_IDSCAN = 8 if(VENDING_WIRE_THROW) V.shoot_inventory = !V.shoot_inventory if(VENDING_WIRE_CONTRABAND) - V.extended_inventory = !V.extended_inventory + V.categories ^= CAT_HIDDEN if(VENDING_WIRE_ELECTRIFY) V.seconds_electrified = 30 if(VENDING_WIRE_IDSCAN) @@ -48,7 +50,7 @@ var/const/VENDING_WIRE_IDSCAN = 8 if(VENDING_WIRE_THROW) V.shoot_inventory = !mended if(VENDING_WIRE_CONTRABAND) - V.extended_inventory = 0 + V.categories &= ~CAT_HIDDEN if(VENDING_WIRE_ELECTRIFY) if(mended) V.seconds_electrified = 0 diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 5a8df08c8a..09608deb5b 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -1,17 +1,38 @@ -#define CAT_NORMAL 0 -#define CAT_HIDDEN 1 -#define CAT_COIN 2 +#define CAT_NORMAL 1 +#define CAT_HIDDEN 2 // also used in corresponding wires/vending.dm +#define CAT_COIN 4 +/** + * Datum used to hold information about a product in a vending machine + */ /datum/data/vending_product - var/product_name = "generic" + var/product_name = "generic" // Display name for the product var/product_path = null - var/amount = 0 - var/price = 0 - var/display_color = "blue" - var/category = CAT_NORMAL - + var/amount = 0 // Amount held in the vending machine + var/price = 0 // Price to buy one + var/display_color = null // Display color for vending machine listing + var/category = CAT_NORMAL // CAT_HIDDEN for contraband, CAT_COIN for premium +/datum/data/vending_product/New(var/path, var/name = null, var/amount = 1, var/price = 0, var/color = null, var/category = CAT_NORMAL) + ..() + + src.product_path = path + + if(!name) + var/atom/tmp = new path + src.product_name = initial(tmp.name) + del(tmp) + else + src.product_name = name + + src.amount = amount + src.price = price + src.display_color = color + src.category = category +/** + * A vending machine + */ /obj/machinery/vending name = "Vendomat" desc = "A generic vending machine." @@ -21,66 +42,105 @@ anchored = 1 density = 1 + var/icon_vend //Icon_state when vending + var/icon_deny //Icon_state when denying access + + // Power use_power = 1 idle_power_usage = 10 var/vend_power_usage = 150 //actuators and stuff + // Vending-related var/active = 1 //No sales pitches if off! var/vend_ready = 1 //Are we ready to vend?? Is it time?? var/vend_delay = 10 //How long does it take to vend? - var/datum/data/vending_product/currently_vending = null // A /datum/data/vending_product instance of what we're paying for right now. - - // To be filled out at compile time + var/categories = CAT_NORMAL // Bitmask of cats we're currently showing + var/datum/data/vending_product/currently_vending = null // What we're requesting payment for right now + + /* + Variables used to initialize the product list + These are used for initialization only, and so are optional if + product_records is specified + */ var/list/products = list() // For each, use the following pattern: var/list/contraband = list() // list(/type/path = amount,/type/path2 = amount2) var/list/premium = list() // No specified amount = only one in stock var/list/prices = list() // Prices for each item, list(/type/path = price), items not in the list don't have a price. - var/product_slogans = "" //String of slogans separated by semicolons, optional - var/product_ads = "" //String of small ad messages in the vending screen - random chance + // List of vending_product items available. var/list/product_records = list() - var/list/hidden_records = list() - var/list/coin_records = list() + + + // Variables used to initialize advertising + var/product_slogans = "" //String of slogans spoken out loud, separated by semicolons + var/product_ads = "" //String of small ad messages in the vending screen + + var/list/ads_list = list() + + // Stuff relating vocalizations var/list/slogan_list = list() - var/list/small_ads = list() // small ad messages in the vending screen - random chance of popping up whenever you open it + var/shut_up = 1 //Stop spouting those godawful pitches! var/vend_reply //Thank you for shopping! var/last_reply = 0 var/last_slogan = 0 //When did we last pitch? var/slogan_delay = 6000 //How long until we can pitch again? - var/icon_vend //Icon_state when vending! - var/icon_deny //Icon_state when vending! - //var/emagged = 0 //Ignores if somebody doesn't have card access to that machine. + + // Things that can go wrong + emagged = 0 //Ignores if somebody doesn't have card access to that machine. var/seconds_electrified = 0 //Shock customers like an airlock. var/shoot_inventory = 0 //Fire items at customers! We're broken! - var/shut_up = 1 //Stop spouting those godawful pitches! - var/extended_inventory = 0 //can we access the hidden inventory? + var/scan_id = 1 var/obj/item/weapon/coin/coin var/datum/wires/vending/wires = null - var/check_accounts = 0 // 1 = requires PIN and checks accounts. 0 = You slide an ID, it vends, SPACE COMMUNISM! - /obj/machinery/vending/New() ..() wires = new(src) spawn(4) - src.slogan_list = text2list(src.product_slogans, ";") + if(src.product_slogans) + src.slogan_list += text2list(src.product_slogans, ";") - // So not all machines speak at the exact same time. - // The first time this machine says something will be at slogantime + this random value, - // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is crated. - src.last_slogan = world.time + rand(0, slogan_delay) + // So not all machines speak at the exact same time. + // The first time this machine says something will be at slogantime + this random value, + // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is crated. + src.last_slogan = world.time + rand(0, slogan_delay) - src.build_inventory(products) - //Add hidden inventory - src.build_inventory(contraband, 1) - src.build_inventory(premium, 0, 1) + if(src.product_ads) + src.ads_list += text2list(src.product_ads, ";") + + src.build_inventory() power_change() return return +/** + * Build src.produdct_records from the products lists + * + * src.products, src.contraband, src.premium, and src.prices allow specifying + * products that the vending machine is to carry without manually populating + * src.product_records. + */ +/obj/machinery/vending/proc/build_inventory() + var/list/all_products = list( + list(src.products, CAT_NORMAL), + list(src.contraband, CAT_HIDDEN), + list(src.premium, CAT_COIN)) + + for(var/current_list in all_products) + var/category = current_list[2] + + for(var/entry in current_list[1]) + var/datum/data/vending_product/product = new/datum/data/vending_product(entry) + + product.price = (entry in src.prices) ? src.prices[entry] : 0 + product.amount = (current_list[1][entry]) ? current_list[1][entry] : 1 + product.category = category + + src.product_records.Add(product) + /obj/machinery/vending/Del() del(wires) // qdel wires = null @@ -116,36 +176,6 @@ return -/obj/machinery/vending/proc/build_inventory(var/list/productlist,hidden=0,req_coin=0) - - for(var/typepath in productlist) - var/amount = productlist[typepath] - var/price = prices[typepath] - if(isnull(amount)) amount = 1 - - var/datum/data/vending_product/R = new /datum/data/vending_product() - - R.product_path = typepath - R.amount = amount - R.price = price - R.display_color = pick("red","blue","green") - - if(hidden) - R.category=CAT_HIDDEN - hidden_records += R - else if(req_coin) - R.category=CAT_COIN - coin_records += R - else - R.category=CAT_NORMAL - product_records += R - - var/atom/temp = typepath - R.product_name = initial(temp.name) - -// world << "Added: [R.product_name]] - [R.amount] - [R.product_path]" - return - /obj/machinery/vending/attackby(obj/item/weapon/W as obj, mob/user as mob) if (currently_vending) var/paid = 0 @@ -207,7 +237,7 @@ else if(src.panel_open) - for(var/datum/data/vending_product/R in product_records) + for(var/datum/data/vending_product/R in product_records) if(istype(W, R.product_path)) stock(R, user) del(W) @@ -346,31 +376,6 @@ /obj/machinery/vending/attack_ai(mob/user as mob) return attack_hand(user) -/obj/machinery/vending/proc/GetProductIndex(var/datum/data/vending_product/P) - var/list/plist - switch(P.category) - if(CAT_NORMAL) - plist=product_records - if(CAT_HIDDEN) - plist=hidden_records - if(CAT_COIN) - plist=coin_records - else - warning("UNKNOWN CATEGORY [P.category] IN TYPE [P.product_path] INSIDE [type]!") - return plist.Find(P) - -/obj/machinery/vending/proc/GetProductByID(var/pid, var/category) - switch(category) - if(CAT_NORMAL) - return product_records[pid] - if(CAT_HIDDEN) - return hidden_records[pid] - if(CAT_COIN) - return coin_records[pid] - else - warning("UNKNOWN PRODUCT: PID: [pid], CAT: [category] INSIDE [type]!") - return null - /obj/machinery/vending/attack_hand(mob/user as mob) if(stat & (BROKEN|NOPOWER)) return @@ -393,28 +398,24 @@ var/dat = "
[vendorname]


" //display the name, and added a horizontal rule dat += "Select an item:

" //the rest is just general spacing and bolding - if (premium.len > 0) - dat += "Coin slot: [coin ? coin : "No coin inserted"] (Remove)
" - if (src.product_records.len == 0) dat += "No product loaded!" else - var/list/display_records = list() - display_records += src.product_records - if(src.extended_inventory) - display_records += src.hidden_records - if(src.coin) - display_records += src.coin_records - - for (var/datum/data/vending_product/R in display_records) - dat += "[R.product_name]:" - dat += " [R.amount] " - if(R.price) - dat += " (Price: [R.price])" - if (R.amount > 0) - var/idx=GetProductIndex(R) - dat += " (Vend)" + for(var/idx = 1 to src.product_records.len) + var/datum/data/vending_product/product = src.product_records[idx] + if(!(product.category & src.categories)) + continue + + if(product.display_color) + dat += "[product.product_name]:" + dat += " [product.amount] " + else + dat += "[product.product_name]: [product.amount]" + if(product.price) + dat += " (Price: [product.price])" + if (product.amount > 0) + dat += " (Vend)" else dat += " SOLD OUT" dat += "
" @@ -424,7 +425,7 @@ if(panel_open) dat += wires() - if(product_slogans != "") + if(slogan_list.len > 0) dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle" user << browse(dat, "window=vending") @@ -471,14 +472,10 @@ flick(icon_deny,src) return - var/idx=text2num(href_list["vend"]) - var/cat=text2num(href_list["cat"]) + var/key = text2num(href_list["vend"]) + var/datum/data/vending_product/R = product_records[key] - var/datum/data/vending_product/R = GetProductByID(idx,cat) - if (!R || !istype(R) || !R.product_path || R.amount <= 0) - return - - if(R.price == null) + if(R.price <= 0) src.vend(R, usr) else src.currently_vending = R @@ -507,7 +504,7 @@ return src.vend_ready = 0 //One thing at a time!! - if (R in coin_records) + if (R.category & CAT_COIN) if(!coin) user << "\blue You need to insert a coin to get this item." return @@ -848,35 +845,32 @@ contraband = list(/obj/item/seeds/amanitamycelium = 2,/obj/item/seeds/glowshroom = 2,/obj/item/seeds/libertymycelium = 2,/obj/item/seeds/mtearseed = 2, /obj/item/seeds/nettleseed = 2,/obj/item/seeds/reishimycelium = 2,/obj/item/seeds/reishimycelium = 2,/obj/item/seeds/shandseed = 2,) premium = list(/obj/item/toy/waterflower = 1) + +/** + * Populate hydroseeds product_records + * + * This needs to be customized to fetch the actual names of the seeds, otherwise + * the machine would simply list "packet of seeds" times 20 + */ +/obj/machinery/vending/hydroseeds/build_inventory() + var/list/all_products = list( + list(src.products, CAT_NORMAL), + list(src.contraband, CAT_HIDDEN), + list(src.premium, CAT_COIN)) -/obj/machinery/vending/hydroseeds/build_inventory(var/list/productlist,hidden=0,req_coin=0) + for(var/current_list in all_products) + var/category = current_list[2] - for(var/typepath in productlist) - var/amount = productlist[typepath] - var/price = prices[typepath] - if(isnull(amount)) amount = 1 + for(var/entry in current_list[1]) + var/obj/item/seeds/S = new entry(src) + var/name = S.name + var/datum/data/vending_product/product = new/datum/data/vending_product(entry, name) - var/datum/data/vending_product/R = new /datum/data/vending_product() - - R.product_path = typepath - R.amount = amount - R.price = price - R.display_color = pick("red","blue","green") - - if(hidden) - R.category=CAT_HIDDEN - hidden_records += R - else if(req_coin) - R.category=CAT_COIN - coin_records += R - else - R.category=CAT_NORMAL - product_records += R - - var/obj/item/seeds/S = new typepath(src) - R.product_name = S.name - del(S) - return + product.price = (entry in src.prices) ? src.prices[entry] : 0 + product.amount = (current_list[1][entry]) ? current_list[1][entry] : 1 + product.category = category + + src.product_records.Add(product) /obj/machinery/vending/magivend name = "MagiVend" From 9dffc8f922233da6d74acc0c0f0c95446eb4b82e Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Sat, 7 Feb 2015 12:06:58 -0500 Subject: [PATCH 5/8] Add NanoUI to vending machines Error messages are now displayed in the NanoUI interface, instead of going to the text log. Wire UI now consistent with other wire-enabled wire devices that use NanoUI. --- code/datums/wires/vending.dm | 5 - code/game/machinery/vending.dm | 156 +++++++++++++++------------- nano/templates/vending_machine.tmpl | 56 ++++++++++ 3 files changed, 141 insertions(+), 76 deletions(-) create mode 100644 nano/templates/vending_machine.tmpl diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 67ca216251..7f9c4ed10a 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -19,11 +19,6 @@ var/const/VENDING_WIRE_IDSCAN = 8 return 1 return 0 -/datum/wires/vending/Interact(var/mob/living/user) - if(CanUse(user)) - var/obj/machinery/vending/V = holder - V.attack_hand(user) - /datum/wires/vending/GetInteractWindow() var/obj/machinery/vending/V = holder . += ..() diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 09608deb5b..282aa9b28c 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -56,6 +56,8 @@ var/vend_delay = 10 //How long does it take to vend? var/categories = CAT_NORMAL // Bitmask of cats we're currently showing var/datum/data/vending_product/currently_vending = null // What we're requesting payment for right now + var/status_message = "" // Status screen messages like "insufficient funds", displayed in NanoUI + var/status_error = 0 // Set to 1 if status_message is an error /* Variables used to initialize the product list @@ -195,9 +197,9 @@ if(paid) src.vend(currently_vending, usr) - currently_vending = null return else if(handled) + nanomanager.update_uis(src) return // don't smack that machine with your 2 thalers if (istype(W, /obj/item/weapon/card/emag)) @@ -210,7 +212,8 @@ src.overlays.Cut() if(src.panel_open) src.overlays += image(src.icon, "[initial(icon_state)]-panel") - src.updateUsrDialog() + + nanomanager.update_uis(src) // Speaker switch is on the main UI, not wires UI return else if(istype(W, /obj/item/device/multitool)||istype(W, /obj/item/weapon/wirecutters)) if(src.panel_open) @@ -220,7 +223,9 @@ user.drop_item() W.loc = src coin = W + categories |= CAT_COIN user << "\blue You insert the [W] into the [src]" + nanomanager.update_uis(src) return else if(istype(W, /obj/item/weapon/wrench)) if(do_after(user, 20)) @@ -237,7 +242,7 @@ else if(src.panel_open) - for(var/datum/data/vending_product/R in product_records) + for(var/datum/data/vending_product/R in product_records) if(istype(W, R.product_path)) stock(R, user) del(W) @@ -252,7 +257,10 @@ */ /obj/machinery/vending/proc/pay_with_cash(var/obj/item/weapon/spacecash/cashmoney, mob/user) if(currently_vending.price > cashmoney.worth) - usr << "\icon[src] That is not enough money." + + // This is not a status display message, since it's something the character + // themselves is meant to see BEFORE putting the money in + usr << "\icon[cashmoney] That is not enough money." return 0 if(istype(cashmoney, /obj/item/weapon/spacecash/bundle)) @@ -294,7 +302,8 @@ /obj/machinery/vending/proc/pay_with_ewallet(var/obj/item/weapon/spacecash/ewallet/wallet) visible_message("[usr] swipes a card through [src].") if(currently_vending.price > wallet.worth) - usr << "\icon[src] Insufficient funds on chargecard." + src.status_message = "Insufficient funds on chargecard." + src.status_error = 1 return 0 else wallet.worth -= currently_vending.price @@ -311,11 +320,13 @@ visible_message("[usr] swipes a card through [src].") var/datum/money_account/customer_account = get_account(I.associated_account_number) if (!customer_account) - usr << "\icon[src] Error: Unable to access account. Please contact technical support if problem persists." + src.status_message = "Error: Unable to access account. Please contact technical support if problem persists." + src.status_error = 1 return 0 if(customer_account.suspended) - usr << "\icon[src] Unable to access account: account suspended." + src.status_message = "Unable to access account: account suspended." + src.status_error = 1 return 0 // Have the customer punch in the PIN before checking if there's enough money. Prevents people from figuring out acct is @@ -325,11 +336,13 @@ customer_account = attempt_account_access(I.associated_account_number, attempt_pin, 2) if(!customer_account) - usr << "\icon[src] Unable to access account: incorrect credentials." + src.status_message = "Unable to access account: incorrect credentials." + src.status_error = 1 return 0 if(currently_vending.price > customer_account.money) - usr << "\icon[src] Insufficient funds in account." + src.status_message = "Insufficient funds in account." + src.status_error = 1 return 0 else // Okay to move the money at this point @@ -379,62 +392,63 @@ /obj/machinery/vending/attack_hand(mob/user as mob) if(stat & (BROKEN|NOPOWER)) return - user.set_machine(src) if(src.seconds_electrified != 0) if(src.shock(user, 100)) return - var/vendorname = (src.name) //import the machine's name + wires.Interact(user) + ui_interact(user) - if(src.currently_vending) - var/dat = "
[vendorname]


" //display the name, and added a horizontal rule - dat += "Product selected: [currently_vending.product_name]
Charge: [currently_vending.price]

Please swipe a card or insert cash to pay for the item.
" - dat += "Cancel" - user << browse(dat, "window=vending") - onclose(user, "") - return - - var/dat = "
[vendorname]


" //display the name, and added a horizontal rule - dat += "Select an item:

" //the rest is just general spacing and bolding - - if (src.product_records.len == 0) - dat += "No product loaded!" +/** + * Display the NanoUI window for the vending machine. + * + * See NanoUI documentation for details. + */ +/obj/machinery/vending/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + user.set_machine(src) + + var/list/data = list() + if(currently_vending) + data["mode"] = 1 + data["product"] = currently_vending.product_name + data["price"] = currently_vending.price + data["message_err"] = 0 + data["message"] = src.status_message + data["message_err"] = src.status_error else + data["mode"] = 0 + var/list/listed_products = list() - for(var/idx = 1 to src.product_records.len) - var/datum/data/vending_product/product = src.product_records[idx] - if(!(product.category & src.categories)) + for(var/key = 1 to src.product_records.len) + var/datum/data/vending_product/I = src.product_records[key] + + if(!(I.category & src.categories)) continue + + listed_products.Add(list(list( + "key" = key, + "name" = I.product_name, + "price" = I.price, + "color" = I.display_color, + "amount" = I.amount))) - if(product.display_color) - dat += "[product.product_name]:" - dat += " [product.amount] " - else - dat += "[product.product_name]: [product.amount]" - if(product.price) - dat += " (Price: [product.price])" - if (product.amount > 0) - dat += " (Vend)" - else - dat += " SOLD OUT" - dat += "
" - - dat += "
" - - if(panel_open) - dat += wires() - - if(slogan_list.len > 0) - dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle" - - user << browse(dat, "window=vending") - onclose(user, "") - return - -// returns the wire panel text -/obj/machinery/vending/proc/wires() - return wires.GetInteractWindow() + data["products"] = listed_products + + if(src.coin) + data["coin"] = src.coin.name + + if(src.panel_open) + data["panel"] = 1 + data["speaker"] = src.shut_up ? 0 : 1 + else + data["panel"] = 0 + + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "vending_machine.tmpl", src.name, 440, 600) + ui.set_initial_data(data) + ui.open() /obj/machinery/vending/Topic(href, href_list) if(stat & (BROKEN|NOPOWER)) @@ -452,9 +466,9 @@ usr.put_in_hands(coin) usr << "\blue You remove the [coin] from the [src]" coin = null + categories &= ~CAT_COIN if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf)))) - usr.set_machine(src) if ((href_list["vend"]) && (src.vend_ready) && (!currently_vending)) if(istype(usr,/mob/living/silicon)) @@ -479,23 +493,17 @@ src.vend(R, usr) else src.currently_vending = R - src.updateUsrDialog() - return + src.status_message = "Please swipe a card or insert cash to pay for the item." + src.status_error = 0 - else if (href_list["cancel_buying"]) + else if (href_list["cancelpurchase"]) src.currently_vending = null - src.updateUsrDialog() - return else if ((href_list["togglevoice"]) && (src.panel_open)) src.shut_up = !src.shut_up src.add_fingerprint(usr) - src.updateUsrDialog() - else - usr << browse(null, "window=vending") - return - return + nanomanager.update_uis(src) /obj/machinery/vending/proc/vend(datum/data/vending_product/R, mob/user) if((!allowed(usr)) && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH @@ -503,7 +511,10 @@ flick(src.icon_deny,src) return src.vend_ready = 0 //One thing at a time!! - + src.status_message = "Vending..." + src.status_error = 0 + nanomanager.update_uis(src) + if (R.category & CAT_COIN) if(!coin) user << "\blue You need to insert a coin to get this item." @@ -514,8 +525,10 @@ else user << "\blue You weren't able to pull the coin out fast enough, the machine ate it, string and all." del(coin) + categories &= ~CAT_COIN else del(coin) + categories &= ~CAT_COIN R.amount-- @@ -529,17 +542,18 @@ flick(src.icon_vend,src) spawn(src.vend_delay) new R.product_path(get_turf(src)) + src.status_message = "" + src.status_error = 0 src.vend_ready = 1 - return - - src.updateUsrDialog() + currently_vending = null + nanomanager.update_uis(src) /obj/machinery/vending/proc/stock(var/datum/data/vending_product/R, var/mob/user) if(src.panel_open) user << "\blue You stock the [src] with \a [R.product_name]" R.amount++ - src.updateUsrDialog() + nanomanager.update_uis(src) /obj/machinery/vending/process() if(stat & (BROKEN|NOPOWER)) diff --git a/nano/templates/vending_machine.tmpl b/nano/templates/vending_machine.tmpl new file mode 100644 index 0000000000..93d5c1007c --- /dev/null +++ b/nano/templates/vending_machine.tmpl @@ -0,0 +1,56 @@ + + +{{if data.mode == 0}} +

Items available

+
+ {{for data.products}} +
+
+ {{if value.price > 0}} + {{:helper.link('Buy (' + value.price + ')', 'cart', { "vend" : value.key }, value.amount > 0 ? null : 'disabled')}} + {{else}} + {{:helper.link('Vend', 'circle-arrow-s', { "vend" : value.key }, value.amount > 0 ? null : 'disabled')}} + {{/if}} +
+
+ {{if value.color}}{{:value.name}} + {{else}}{{:value.name}} + {{/if}} + ({{:value.amount ? value.amount : "NONE LEFT"}}) +
+
+ {{empty}} + No items available! + {{/for}} +
+{{if data.coin}} +

Coin

+
+
Coin deposited:
+
{{:helper.link(data.coin, 'eject', {'remove_coin' : 1})}}
+
+{{/if}} +{{else data.mode == 1}} +

Item selected

+
+
+
Item selected:
{{:data.product}}
+
Charge:
{{:data.price}}
+
+
+ {{if data.message_err}} {{/if}} {{:data.message}} +
+
+ {{:helper.link('Cancel', 'arrowreturn-1-w', {'cancelpurchase' : 1})}} +
+
+{{/if}} +{{if data.panel}} +

Maintenance panel

+
+
Speaker
{{:helper.link(data.speaker ? 'Enabled' : 'Disabled', 'gear', {'togglevoice' : 1})}}
+
+{{/if}} \ No newline at end of file From cee2d2502a68cf5711cfbcc52239bb422f7028fb Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Mon, 9 Feb 2015 08:43:57 -0500 Subject: [PATCH 6/8] Make vending machines stop selling things if vendor account is suspended --- code/game/machinery/vending.dm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 282aa9b28c..482a36e0d4 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -179,7 +179,7 @@ return /obj/machinery/vending/attackby(obj/item/weapon/W as obj, mob/user as mob) - if (currently_vending) + if (currently_vending && vendor_account && !vendor_account.suspended) var/paid = 0 var/handled = 0 if(istype(W, /obj/item/weapon/card/id)) @@ -493,8 +493,12 @@ src.vend(R, usr) else src.currently_vending = R - src.status_message = "Please swipe a card or insert cash to pay for the item." - src.status_error = 0 + if(!vendor_account || vendor_account.suspended) + src.status_message = "This machine is currently unable to process payments due to problems with the associated account." + src.status_error = 1 + else + src.status_message = "Please swipe a card or insert cash to pay for the item." + src.status_error = 0 else if (href_list["cancelpurchase"]) src.currently_vending = null @@ -611,7 +615,7 @@ new dump_path(src.loc) R.amount-- break - + stat |= BROKEN src.icon_state = "[initial(icon_state)]-broken" return From 7572ec427a7b2256b286f6b6f34e53448b7a026a Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Wed, 11 Feb 2015 20:02:23 -0500 Subject: [PATCH 7/8] Add an extra check to vending's Topic() Topic() now checks if it really can vend items with current category config, in case the request was malformed/rewritten --- code/game/machinery/vending.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 482a36e0d4..f88b7895ae 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -488,7 +488,11 @@ var/key = text2num(href_list["vend"]) var/datum/data/vending_product/R = product_records[key] - + + // This should not happen unless the request from NanoUI was bad + if(!(R.category & src.categories)) + return + if(R.price <= 0) src.vend(R, usr) else From 82cab6635b4551a3b14f9c253e13340cd4b64981 Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Wed, 11 Feb 2015 20:05:04 -0500 Subject: [PATCH 8/8] Add notes on vending changes to changelog --- html/changelog.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/html/changelog.html b/html/changelog.html index 3a214316b3..3092f99714 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -56,6 +56,14 @@ should be listed in the changelog upon commit though. Thanks. --> +
+

12 February 2015

+

Daranz updated:

+
    +
  • Vending machines now use NanoUI and accept cash. The vendor account can now be suspended to disable all sales in all machines on station.
  • +
+
+

4 February 2015

RavingManiac updated: