Files
Bubberstation/code/modules/vending/custom.dm
SyncIt21 b6faafdcb2 Fixes custom vendor item interaction (#93500)
## About The Pull Request
- Fixes #93498

## Changelog
🆑
fix: restores custom vendor item interaction behaviors
/🆑
2025-10-19 04:55:44 +02:00

352 lines
11 KiB
Plaintext

///This unique key decides how items are stacked on the UI. We separate them based on name,price & type
#define ITEM_HASH(item)("[item.name][item.custom_price][item.type]")
/obj/machinery/vending/custom
name = "Custom Vendor"
icon_state = "custom"
icon_deny = "custom-deny"
max_integrity = 400
payment_department = NO_FREEBIES
light_mask = "custom-light-mask"
panel_type = "panel20"
refill_canister = /obj/item/vending_refill/custom
fish_source_path = /datum/fish_source/vending/custom
obj_flags = UNIQUE_RENAME
/// max number of items that the custom vendor can hold
var/max_loaded_items = 20
/// where the money is sent
VAR_PRIVATE/datum/bank_account/linked_account
/// Base64 cache of custom icons.
VAR_PRIVATE/static/list/base64_cache = list()
/obj/machinery/vending/custom/on_deconstruction(disassembled)
var/obj/item/vending_refill/custom/installed_refill = locate() in component_parts
if(linked_account)
//we delete the canister so players don't resell our products as their own
component_parts -= installed_refill
qdel(installed_refill)
//self destruct protocol for unauthorized destruction
explosion(get_turf(src), devastation_range = -1, light_impact_range = 3)
return
//copy product hash keys
installed_refill.products.Cut()
installed_refill.products += products
//move products to canister
for(var/obj/item/stored_item in contents - component_parts)
stored_item.forceMove(installed_refill)
/obj/machinery/vending/custom/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(panel_open && istype(held_item, refill_canister))
context[SCREENTIP_CONTEXT_LMB] = "Restock vending machine"
return CONTEXTUAL_SCREENTIP_SET
if(isliving(user) && istype(held_item, /obj/item/card/id))
var/obj/item/card/id/card_used = held_item
if(card_used?.registered_account)
if(!linked_account)
context[SCREENTIP_CONTEXT_LMB] = "Link account"
return ITEM_INTERACT_SUCCESS
else if(linked_account == card_used.registered_account)
context[SCREENTIP_CONTEXT_LMB] = "Unlink account"
return ITEM_INTERACT_SUCCESS
return ..()
/obj/machinery/vending/custom/examine(mob/user)
. = ..()
if(linked_account)
. += span_warning("Machine is ID locked. Be sure to unlink before deconstructing the machine.")
/obj/machinery/vending/custom/Exited(obj/item/gone, direction)
. = ..()
var/hash_key = ITEM_HASH(gone)
if(products[hash_key])
var/new_amount = products[hash_key] - 1
if(!new_amount)
products -= hash_key
update_static_data_for_all_viewers()
else
products[hash_key] = new_amount
///Returns the number of products loaded in this machine
/obj/machinery/vending/custom/proc/loaded_items()
PRIVATE_PROC(TRUE)
SHOULD_BE_PURE(TRUE)
. = 0
for(var/product_hash in products)
. += products[product_hash]
/obj/machinery/vending/custom/canLoadItem(obj/item/loaded_item, mob/user, send_message = TRUE)
if(loaded_item.flags_1 & HOLOGRAM_1)
if(send_message)
speak("This vendor cannot accept nonexistent items.")
return FALSE
if(isstack(loaded_item))
if(send_message)
speak("Loose items may cause problems, try to use it inside wrapping paper.")
return FALSE
if(!loaded_item.custom_price)
if(send_message)
speak("Item needs to have a custom price set.")
return FALSE
return TRUE
/obj/machinery/vending/custom/loadingAttempt(obj/item/inserted_item, mob/user)
if(!canLoadItem(inserted_item, user))
return FALSE
if(loaded_items() == max_loaded_items)
speak("There are too many items in stock.")
return FALSE
if(!user.transferItemToLoc(inserted_item, src))
to_chat(user, span_warning("[inserted_item] is stuck in your hand!"))
return FALSE
//the hash key decides how items stack in the UI. We diffrentiate them based on name & price
var/hash_key = ITEM_HASH(inserted_item)
if(products[hash_key])
products[hash_key]++
else
products[hash_key] = 1
update_static_data_for_all_viewers()
return TRUE
/obj/machinery/vending/custom/RefreshParts()
SHOULD_CALL_PARENT(FALSE)
restock(locate(refill_canister) in component_parts)
/obj/machinery/vending/custom/restock(obj/item/vending_refill/canister)
. = 0
if(!canister.products?.len)
if(!canister.products)
canister.products = list()
return
var/update_static_data = FALSE
var/available_load = max_loaded_items - loaded_items()
for(var/product_hash in canister.products)
//get available space
var/load_count = min(canister.products[product_hash], available_load)
if(!load_count)
break
//update canister record
canister.products[product_hash] -= load_count
if(!canister.products[product_hash])
canister.products -= product_hash
//update vendor record
products[product_hash] += load_count
//reduce from available space
available_load -= load_count
//update product
for(var/obj/item/product in canister)
if(!load_count)
break
if(ITEM_HASH(product) == product_hash)
. += 1
product.forceMove(src)
load_count--
if(update_static_data)
update_static_data_for_all_viewers()
/obj/machinery/vending/custom/post_restock(mob/living/user, restocked)
if(!restocked)
to_chat(user, span_warning("There's nothing to restock!"))
return
to_chat(user, span_notice("You loaded [restocked] items in [src]"))
/obj/machinery/vending/custom/crowbar_act(mob/living/user, obj/item/attack_item)
if(linked_account)
visible_message(
span_warning("Security warning"),
span_warning("Unauthorized deconstruction of vending machine is prohibited. Please read the warning alert")
)
if(tgui_alert(user, "Vending machine is ID locked.\
Deconstruction will result in an catrostrophic self destruct.\
If you are the owner of this machine please unlink your account with an ID swipe before proceeding.\
Still proceed?",
"Vandalism protection protocol",
list("Yes", "No")) == "No")
return ITEM_INTERACT_FAILURE
return ..()
/obj/machinery/vending/custom/compartmentLoadAccessCheck(mob/user)
. = FALSE
if(!isliving(user))
return FALSE
var/mob/living/living_user = user
var/obj/item/card/id/id_card = living_user.get_idcard(FALSE)
if(id_card?.registered_account && id_card.registered_account == linked_account)
return TRUE
/obj/machinery/vending/custom/item_interaction(mob/living/user, obj/item/attack_item, list/modifiers)
if(isliving(user) && istype(attack_item, /obj/item/card/id))
var/obj/item/card/id/card_used = attack_item
if(card_used?.registered_account)
if(!linked_account)
linked_account = card_used.registered_account
speak("\The [src] has been linked to [card_used].")
return ITEM_INTERACT_SUCCESS
else if(linked_account == card_used.registered_account)
linked_account = null
speak("account unlinked.")
return ITEM_INTERACT_SUCCESS
else
to_chat(user, "verification failed. unlinking process has been cancelled.")
return ITEM_INTERACT_FAILURE
return ..()
/obj/machinery/vending/custom/descformat(input, mob/living/user)
. = input
var/new_slogan = reject_bad_text(tgui_input_text(user, "Set slogan", "Slogan", "Epic", max_length = 60))
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
if (new_slogan)
slogan_list += new_slogan
last_slogan = world.time + rand(0, slogan_delay)
/obj/machinery/vending/custom/collect_records_for_static_data(list/records, list/categories, premium)
. = list()
if(records != product_records) //no coin or hidden stuff only product records
return
categories["Products"] = list("icon" = "cart-shopping")
for(var/stocked_hash in products)
var/base64 = ""
var/obj/item/target = null
for(var/obj/item/stored_item in contents - component_parts)
if(ITEM_HASH(stored_item) == stocked_hash)
base64 = base64_cache[stocked_hash]
if(!base64) //generate an icon of the item to use in UI
base64 = icon2base64(getFlatIcon(stored_item, no_anim = TRUE))
base64_cache[stocked_hash] = base64
target = stored_item
break
. += list(list(
path = stocked_hash,
name = target.name,
price = target.custom_price,
category = "Products",
ref = stocked_hash,
colorable = FALSE,
image = base64
))
/obj/machinery/vending/custom/ui_interact(mob/user, datum/tgui/ui)
if(!linked_account)
balloon_alert(user, "no registered owner!")
return FALSE
return ..()
/obj/machinery/vending/custom/ui_data(mob/user)
. = ..()
var/is_owner = compartmentLoadAccessCheck(user)
.["stock"] = list()
for(var/stocked_hash in products)
.["stock"][stocked_hash] = list(
amount = products[stocked_hash],
free = is_owner
)
/obj/machinery/vending/custom/vend(list/params, mob/living/user, list/greyscale_colors)
. = FALSE
if(!isliving(user))
return
var/obj/item/dispensed_item = params["ref"]
for(var/obj/item/product in contents - component_parts)
if(ITEM_HASH(product) == dispensed_item)
dispensed_item = product
break
if(QDELETED(dispensed_item))
return
var/obj/item/card/id/id_card = user.get_idcard(TRUE)
if(QDELETED(id_card))
balloon_alert(user, "no card found!")
flick(icon_deny, src)
return
/// Charges the user if its not the owner
var/datum/bank_account/payee = id_card.registered_account
if(!compartmentLoadAccessCheck(user))
if(!payee.has_money(dispensed_item.custom_price))
balloon_alert(user, "insufficient funds!")
return
/// Make the transaction
payee.adjust_money(-dispensed_item.custom_price, , "Vending: [dispensed_item]")
linked_account.adjust_money(dispensed_item.custom_price, "Vending: [dispensed_item] Bought")
linked_account.bank_card_talk("[payee.account_holder] made a [dispensed_item.custom_price] \
cr purchase at your custom vendor.")
/// Log the transaction
SSblackbox.record_feedback("amount", "vending_spent", dispensed_item.custom_price)
log_econ("[dispensed_item.custom_price] credits were spent on [src] buying a \
[dispensed_item] by [payee.account_holder], owned by [linked_account.account_holder].")
/// Make an alert
var/ref = REF(user)
if(last_shopper != ref || purchase_message_cooldown < world.time)
speak("Thank you for your patronage [user]!")
purchase_message_cooldown = world.time + 5 SECONDS
last_shopper = ref
/// Remove the item
use_energy(active_power_usage)
try_put_in_hand(dispensed_item, user)
return TRUE
/obj/item/vending_refill/custom
machine_name = "Custom Vendor"
icon_state = "refill_custom"
custom_premium_price = PAYCHECK_CREW
/obj/item/vending_refill/custom/get_part_rating()
. = 0
for(var/key in products)
. += products[key]
/obj/machinery/vending/custom/unbreakable
name = "Indestructible Vendor"
resistance_flags = INDESTRUCTIBLE
allow_custom = FALSE
/obj/machinery/vending/custom/greed //name and like decided by the spawn
icon_state = "greed"
icon_deny = "greed-deny"
panel_type = "panel4"
max_integrity = 700
max_loaded_items = 40
light_mask = "greed-light-mask"
allow_custom = FALSE
custom_materials = list(/datum/material/gold = SHEET_MATERIAL_AMOUNT * 5)
/obj/machinery/vending/custom/greed/Initialize(mapload)
. = ..()
//starts in a state where you can move it
set_anchored(FALSE)
set_panel_open(TRUE)
//and references the deity
name = "[GLOB.deity]'s Consecrated Vendor"
desc = "A vending machine created by [GLOB.deity]."
slogan_list = list("[GLOB.deity] says: It's your divine right to buy!")
add_filter("vending_outline", 9, list("type" = "outline", "color" = COLOR_VERY_SOFT_YELLOW))
add_filter("vending_rays", 10, list("type" = "rays", "size" = 35, "color" = COLOR_VIVID_YELLOW))
#undef ITEM_HASH