Fix the vending machine chef's compartment (#18626)

* Make the vending chef compartment work again

* Fix vending load access check

* Fix missing table wrapper

* Apply eslint fixes & fix button disable check

* Add icons to custom vending items

* Appease the linter

* Rename "dispense" to "vend_custom"

* Refactor vending code out into procs

Reduces code duplication significantly

* Remove stray vend_ready set

* Fix proper/improper names not dispensing

* Remove empty stocks from listing

Bibby

---------

Co-authored-by: ynot01 <ynot000001@gmail.com>
This commit is contained in:
Henry
2023-04-25 23:04:03 +01:00
committed by GitHub
parent 6120cccd7f
commit f4acec19e0
3 changed files with 201 additions and 71 deletions

View File

@@ -37,6 +37,26 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
///List of items that have been returned to the vending machine.
var/list/returned_products
/**
* # User-inserted custom product
* A datum that represents a custom product that is vendable
*/
/datum/data/vending_custom_product
///Name of the stored item
name = "generic"
///Unstripped name of the item, includes article
var/full_name = ""
///Icon of the item
var/asset = null
///How many are stored currently
var/amount = 0
/datum/data/vending_custom_product/New(obj/item/I)
name = format_text(I.name)
full_name = I.name
var/icon/icon = icon(I.icon, I.icon_state, SOUTH, 1)
asset = icon2base64(icon) // costly? probably. less costly than sending the entire spritesheet? also probably
/**
* # vending machines
*
@@ -136,6 +156,7 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
var/obj/item/coin/coin
///Bills we accept?
var/obj/item/stack/spacecash/bill
///Custom item price
var/chef_price = 10
///Default price of items if not overridden
var/default_price = 25
@@ -152,6 +173,7 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
///ID's that can load this vending machine wtih refills
var/list/canload_access_list
///Custom item stock
var/list/vending_machine_input = list()
///Display header on the input view
var/input_display_header = "Custom Compartment"
@@ -601,10 +623,12 @@ GLOBAL_LIST_EMPTY(vending_products)
LAZYADD(product_datum.returned_products, I)
return
if(vending_machine_input[format_text(I.name)])
vending_machine_input[format_text(I.name)]++
else
vending_machine_input[format_text(I.name)] = 1
var/name = format_text(I.name)
if(!vending_machine_input[name])
vending_machine_input[name] = new /datum/data/vending_custom_product(I)
var/datum/data/vending_custom_product/P = vending_machine_input[name]
P.amount++
loaded_items++
/obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/W)
@@ -669,6 +693,9 @@ GLOBAL_LIST_EMPTY(vending_products)
. = list()
.["onstation"] = onstation
.["department"] = payment_department
.["chef"] = list() // "chef compartment" i.e. player-added stock
.["chef"]["title"] = input_display_header
.["chef"]["price"] = chef_price
.["product_records"] = list()
for (var/datum/data/vending_product/R in product_records)
var/list/data = list(
@@ -733,6 +760,11 @@ GLOBAL_LIST_EMPTY(vending_products)
for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records)
.["stock"][R.name] = R.amount
.["extended_inventory"] = extended_inventory
// extra items that have been placed in custom stock
.["custom_stock"] = list()
for (var/name in vending_machine_input)
var/datum/data/vending_custom_product/P = vending_machine_input[name]
.["custom_stock"][P.name] = list(amount = P.amount, img = P.asset)
/obj/machinery/vending/ui_act(action, params)
. = ..()
@@ -770,25 +802,26 @@ GLOBAL_LIST_EMPTY(vending_products)
flick(icon_deny,src)
vend_ready = TRUE
return
var/mob/living/L
if(isliving(usr))
L = usr
if(onstation && ishuman(usr) && (L && !L.ignores_capitalism))
if(coin_records.Find(R) || hidden_records.Find(R))
price_to_use = R.custom_premium_price ? R.custom_premium_price : extra_price
if(LAZYLEN(R.returned_products))
price_to_use = 0 //returned items are free
if(!charge_user(price_to_use, R.name))
vend_ready = TRUE
return
if(onstation && ishuman(usr))
var/mob/living/carbon/human/H = usr
var/obj/item/card/id/C = H.get_idcard(TRUE)
// this should really be caught by charge_user above, just extra safety
if(!C)
say("No card found.")
flick(icon_deny,src)
vend_ready = TRUE
return
else if (!C.registered_account)
say("No account found.")
flick(icon_deny,src)
vend_ready = TRUE
return
else if(age_restrictions && R.age_restricted && (!C.registered_age || C.registered_age < AGE_MINOR))
if(age_restrictions && R.age_restricted && (!C.registered_age || C.registered_age < AGE_MINOR))
say("You are not of legal age to purchase [R.name].")
if(!(usr in GLOB.narcd_underages))
alertradio.set_frequency(FREQ_SECURITY)
@@ -797,29 +830,10 @@ GLOBAL_LIST_EMPTY(vending_products)
flick(icon_deny,src)
vend_ready = TRUE
return
var/datum/bank_account/account = C.registered_account
if(account.account_job && account.account_job.paycheck_department == payment_department)
price_to_use = 0
if(coin_records.Find(R) || hidden_records.Find(R))
price_to_use = R.custom_premium_price ? R.custom_premium_price : extra_price
if(LAZYLEN(R.returned_products))
price_to_use = 0 //returned items are free
if(price_to_use && !account.adjust_money(-price_to_use))
say("You do not possess the funds to purchase [R.name].")
flick(icon_deny,src)
vend_ready = TRUE
return
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
if(D)
D.adjust_money(price_to_use)
if(last_shopper != usr || purchase_message_cooldown < world.time)
say("Thank you for shopping with [src]!")
purchase_message_cooldown = world.time + 5 SECONDS
last_shopper = usr
use_power(5)
if(icon_vend) //Show the vending animation if needed
flick(icon_vend,src)
playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3)
thank_user("Thank you for shopping with [src]!")
finish_vend()
var/obj/item/vended_item
if(!LAZYLEN(R.returned_products)) //always give out free returned stuff first, e.g. to avoid walling a traitor objective in a bag behind paid items
vended_item = new R.product_path(get_turf(src))
@@ -830,6 +844,88 @@ GLOBAL_LIST_EMPTY(vending_products)
R.amount--
SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]"))
vend_ready = TRUE
if("vend_custom")
. = TRUE
if(!vend_ready)
return
if(panel_open)
to_chat(usr, span_warning("The vending machine cannot dispense products while its service panel is open!"))
return
var/N = params["item"]
var/datum/data/vending_custom_product/P = vending_machine_input[N]
if(!P || P.amount <= 0) // don't dispense none item with left beef
return
vend_ready = FALSE //One thing at a time!!
// Charge the user
if (!charge_user(chef_price, P.full_name))
vend_ready = TRUE
return
thank_user("Thank you for shopping local and buying \the [P.full_name]!")
finish_vend()
P.amount = max(P.amount - 1, 0)
for(var/obj/item/I in contents)
if(format_text(I.name) == N)
I.forceMove(get_turf(src))
break
if(P.amount <= 0) // If there's no more left, clear it from the records
vending_machine_input[N] = null
qdel(P)
vend_ready = TRUE
/**
* Charge the user during a vend
* Returns false if the user could not buy this item
*/
/obj/machinery/vending/proc/charge_user(price, item_name)
var/mob/living/L
if(isliving(usr))
L = usr
if(onstation && ishuman(usr) && (L && !L.ignores_capitalism))
var/mob/living/carbon/human/H = usr
var/obj/item/card/id/C = H.get_idcard(TRUE)
if(!C)
say("No card found.")
flick(icon_deny,src)
return FALSE
else if (!C.registered_account)
say("No account found.")
flick(icon_deny,src)
return FALSE
var/datum/bank_account/account = C.registered_account
if(account.account_job && account.account_job.paycheck_department == payment_department)
price = 0
if(price && !account.adjust_money(-price))
say("You do not possess the funds to purchase \the [item_name].")
flick(icon_deny,src)
return FALSE
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
if(D)
D.adjust_money(price)
return TRUE
/**
* Thank the user for the purchase
*/
/obj/machinery/vending/proc/thank_user(message)
if(last_shopper != usr || purchase_message_cooldown < world.time)
say(message)
purchase_message_cooldown = world.time + 5 SECONDS
last_shopper = usr
/**
* Finish a vend by consuming power, playing animations & playing sounds
*/
/obj/machinery/vending/proc/finish_vend()
use_power(5)
if(icon_vend) //Show the vending animation if needed
flick(icon_vend,src)
playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3)
/obj/machinery/vending/process(delta_time)
if(stat & (BROKEN|NOPOWER))
@@ -965,21 +1061,26 @@ GLOBAL_LIST_EMPTY(vending_products)
if(!canload_access_list)
return TRUE
else
var/do_you_have_access = FALSE
var/req_access_txt_holder = req_access_txt
for(var/i in canload_access_list)
req_access_txt = i
if(!allowed(user) && !(obj_flags & EMAGGED) && scan_id)
continue
else
do_you_have_access = TRUE
break //you passed don't bother looping anymore
req_access_txt = req_access_txt_holder // revert to normal (before the proc ran)
if(do_you_have_access)
if((obj_flags & EMAGGED) || !scan_id)
return TRUE
else
to_chat(user, span_warning("[src]'s input compartment blinks red: Access denied."))
return FALSE
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/card/id/C = H.get_idcard(TRUE)
if(!C)
to_chat(user, span_warning("[src]'s input compartment blinks red: No card found."))
return FALSE
var/A = C.GetAccess()
// This is checking like `req_one_access` does (need any in list)
// The only thing that uses this is the chef compartment which only has one access in the list anyway
// If you add another, you may need to alter this behaviour
for(var/req in canload_access_list)
if(req in A)
return TRUE
to_chat(user, span_warning("[src]'s input compartment blinks red: Access denied."))
return FALSE
/obj/machinery/vending/onTransitZ()
return

View File

@@ -41,13 +41,6 @@
S.forceMove(get_turf(src))
return ..()
/obj/machinery/vending/snack/proc/food_load(obj/item/reagent_containers/food/snacks/S)
if(vending_machine_input[S.name])
vending_machine_input[S.name]++
else
vending_machine_input[S.name] = 1
sortList(vending_machine_input)
/obj/machinery/vending/snack/random
name = "\improper Random Snackies"
icon_state = "random_snack"

View File

@@ -24,12 +24,24 @@ const VendingRow = (props, context) => {
|| data.ignores_capitalism
// yogs end
);
const customFree = (
!data.onstation
|| (
data.user
&& data.department
&& data.department === data.user.department
)
|| data.ignores_capitalism
);
return (
<Table.Row>
<Table.Cell collapsing>
{product.base64 ? (
<img
src={`data:image/jpeg;base64,${product.img}`}
className="icon"
style={{
'vertical-align': 'middle',
'horizontal-align': 'middle',
@@ -51,8 +63,7 @@ const VendingRow = (props, context) => {
</Table.Cell>
<Table.Cell collapsing textAlign="center">
<Box
color={custom
? 'good'
color={custom ? (productStock > 0 ? 'good' : 'bad')
: productStock <= 0
? 'bad'
: productStock <= (product.max_amount / 2)
@@ -65,8 +76,13 @@ const VendingRow = (props, context) => {
{custom && (
<Button
fluid
content={data.access ? 'FREE' : product.price + ' cr'}
onClick={() => act('dispense', {
disabled={(
productStock === 0
|| !data.user
|| (!free && product.price > data.user.cash)
)}
content={customFree ? 'FREE' : product.price + ' cr'}
onClick={() => act('vend_custom', {
'item': product.name,
})} />
) || (
@@ -93,13 +109,10 @@ export const Vending = (props, context) => {
const { act, data } = useBackend(context);
const {
product_ad,
chef,
} = data;
let inventory;
let custom = false;
if (data.vending_machine_input) {
inventory = data.vending_machine_input;
custom = true;
} else if (data.extended_inventory) {
if (data.extended_inventory) {
inventory = [
...data.product_records,
...data.coin_records,
@@ -111,6 +124,17 @@ export const Vending = (props, context) => {
...data.coin_records,
];
}
const customInventory = Object.keys(data.custom_stock).map(itemName => ({
product: {
name: itemName,
price: chef.price,
base64: true,
img: data.custom_stock[itemName].img,
},
productStock: data.custom_stock[itemName].amount,
}));
return (
<Window
title="Vending Machine"
@@ -140,12 +164,24 @@ export const Vending = (props, context) => {
)}
</Section>
)}
{!!customInventory.length &&
<Section title={chef.title} >
<Table>
{customInventory.map(customItem => (
<VendingRow
key={customItem.product.name}
custom
product={customItem.product}
productStock={customItem.productStock}
/>
))}
</Table>
</Section>}
<Section title="Products" >
<Table>
{inventory.map(product => (
<VendingRow
key={product.name}
custom={custom}
product={product}
productStock={data.stock[product.name]} />
))}