From 71f0806bbc3c5984270a8f065dc27240ab5e51f4 Mon Sep 17 00:00:00 2001 From: Drathek <76988376+Drulikar@users.noreply.github.com> Date: Wed, 15 May 2024 06:19:10 -0700 Subject: [PATCH] Med Vendor Rebalance (#6103) # About the pull request This PR is a follow up to #5677 and does many things: - ~~Disables splitting of medical items.~~ - ~~In the case of splints, to avoid splitting them via applying and removing them, recovered splints are considered contaminated. However, you can get them to not be deemed contaminated if you form a full stack with them.~~ - Increases the starting stock of several medical vendors, also shifting more of the bottles to chem vendors. - Adds periodic automatic restocking to med linked medical vendors if they are operable. - Initializes groundside medical vendors with randomly reduced stock amounts and reagents (if not on WO mode). - Disables refilling medical items (as in use 3 splints on a vendor and get upgraded to 5). Some conveyed to me they would rather lose the refilling mechanic over the ability to split stacks. - Autoinjectors and bottles can still be refilled using now internal reagents tanks in the vendor. If the vendor has a medlink this will periodically recharge. The UI also displays the reagent amount much like the chem dispenser does. - Adds the ability to bulk restock vendors with storage items. Click drag the pack onto the vendor and all items that can restock the vendor will be placed in the vendor. - Adds the ability to bulk restock medical vendors with restock carts ordered from requisitions. There are two variants: one that restocks the items and another that restocks the internal reagent tanks. They can be disassembled with a wrench. - Adds the ability to restock partial stacks. - Doubles the starting energy amount for chemical dispensers by increasing the max energy amount in chem_storage and increasing the boost gained when a chemical dispenser is connected to the chem_storage. - Discounts all requisitions medical supplies. - Enables initial population scaling for blood bags (of note it wasn't intended these could restock automatically with a medlink; the supply restock cart does work on them though and I intend to keep this) This PR also fixes some miscellaneous things: - Using a item on a medvendor will now try to fill the vendor with that item (rather than only performing this action with a mouse drop). - Fixes some of the grammar issues regarding restocking. - Fixes some unused squad prep vendors that were not properly adding headsets to their list of items. - Fixes removing nano splints creating splints with 0 amount. - Fixes xenos being able to restock vendors. - Tweaks the alignment of the images of items in vendors to be more centered vertically. - Tweaks the widths of tables in vendors. - Fixes USCM theme vendors not showing alternating backgrounds for odd rows **Of note: The restock cart sprites are temporary.** # Explain why it's good for the game The ultimate goal of this PR is to remove the infinite quantity of medical items groundside so there is more pressure on requisitions to keep groundside supplied. See https://github.com/orgs/cmss13-devs/projects/6/views/1?pane=issue&itemId=23005516 for a broader picture of the changes that this PR is helping push towards. I will finalize a design document once the remaining aspects for requisitions are decided. It also is a little goofy that a colony that got decimated by a xenomorph infestation would have fully stocked medical supplies. # Testing Photographs and Procedure
Screenshots & Videos Original implementation: https://youtu.be/6ES88Zre6Gg Revised implementation: https://youtu.be/7Iyy3eKx3Ng Bulk restocking using inventories: https://github.com/cmss13-devs/cmss13/assets/76988376/b9c33693-fe95-410e-8841-d962b052a5fb Restock carts (temporary sprites for now): ![image](https://github.com/cmss13-devs/cmss13/assets/76988376/1e3e537c-f3a8-4e02-8e55-c97db707748b) UI polishing: ![image](https://github.com/cmss13-devs/cmss13/assets/76988376/087f2bf2-9b2a-41e2-9786-4e4c6464a397) ![image](https://github.com/cmss13-devs/cmss13/assets/76988376/9bc03db2-f244-47aa-90f9-0ccdf398acf7)
# Changelog :cl: Drathek balance: Increased the starting stock of most medical vendors but shifts some reagent bottles from weymeds to weychems balance: Med linked medical vendors will now automatically restock items (requires 20 minutes from round start) and reagents (no time requirement) periodically if operable balance: Groundside medical vendors will now have random stock and reagents missing if its not WO balance: Partial medical item stacks can no longer be refilled at vendors (autoinjectors and bottles can still be refilled pulling from internal reagent tanks) balance: Doubles the starting energy for chemical dispensers balance: Discounts all requisitions medical supplies ui: Added reagent amount display to vendors that have internal reagent tanks and tweaked the icon positioning of items slightly ui: Tweaked table widths in vendors ui: Fixes USCM theme vendors not showing different backgrounds for odd rows qol: Restocking a medvendor manually can now be done with just a click rather than only via mouse drop qol: Restocking a vendor can now be performed in bulk from a storage inventory - click drag the inventory to the vendor and you will restock all the items you can add: Vendors can accept partial stacks when restocking - when an item has a partial quantity for a stack it is denoted with an asterisk add: Added two restock carts for bulk restocking medical vendors, one for items and the other for reagents (purchasable from requisitions - disassemble with a wrench - temporary sprites for now) fix: Fixed some currently unused squad prep vendors fix: Removing nano splints on a person no longer creates a 0 amount normal splint spellcheck: Tweaked some restock messages and vendor descriptions /:cl: --- code/datums/supply_packs/medical.dm | 32 +- code/game/machinery/vending/cm_vending.dm | 217 ++++-- .../machinery/vending/vendor_types/medical.dm | 693 +++++++++++++----- .../vendor_types/squad_prep/squad_prep.dm | 4 + code/game/objects/items/stacks/stack.dm | 66 +- code/modules/mob/living/carbon/human/human.dm | 77 +- .../chemical_research/Chemical-Research.dm | 4 +- .../chemistry_machinery/chem_storage.dm | 2 +- .../vehicles/interior/interactable/vendors.dm | 13 - .../tgui/interfaces/VendingSorted.tsx | 45 +- .../tgui/styles/interfaces/VendingSorted.scss | 9 +- tgui/packages/tgui/styles/themes/uscm.scss | 5 + 12 files changed, 827 insertions(+), 340 deletions(-) diff --git a/code/datums/supply_packs/medical.dm b/code/datums/supply_packs/medical.dm index 097642eb163f..acfb9fe1793d 100644 --- a/code/datums/supply_packs/medical.dm +++ b/code/datums/supply_packs/medical.dm @@ -17,11 +17,31 @@ /obj/item/storage/pill_bottle/peridaxon, /obj/item/storage/box/pillbottles, ) - cost = 20 + cost = 15 containertype = /obj/structure/closet/crate/medical containername = "medical crate" group = "Medical" +/datum/supply_packs/medical_restock_cart + name = "medical restock cart" + contains = list( + /obj/structure/restock_cart/medical, + ) + cost = 20 + containertype = null + containername = "medical restock cart" + group = "Medical" + +/datum/supply_packs/medical_reagent_cart + name = "medical reagent restock cart" + contains = list( + /obj/structure/restock_cart/medical/reagent, + ) + cost = 20 + containertype = null + containername = "medical reagent restock cart" + group = "Medical" + /datum/supply_packs/pillbottle name = "pill bottle crate (x2 each)" contains = list( @@ -42,7 +62,7 @@ /obj/item/storage/box/pillbottles, /obj/item/storage/box/pillbottles, ) - cost = 20 + cost = 15 containertype = /obj/structure/closet/crate/medical containername = "medical crate" group = "Medical" @@ -61,7 +81,7 @@ /obj/item/storage/firstaid/adv, /obj/item/storage/firstaid/adv, ) - cost = 16 + cost = 11 containertype = /obj/structure/closet/crate/medical containername = "medical crate" group = "Medical" @@ -74,7 +94,7 @@ /obj/item/storage/box/bodybags, /obj/item/storage/box/bodybags, ) - cost = 12 + cost = 7 containertype = /obj/structure/closet/crate/medical containername = "body bag crate" group = "Medical" @@ -86,7 +106,7 @@ /obj/item/bodybag/cryobag, /obj/item/bodybag/cryobag, ) - cost = 20 + cost = 15 containertype = /obj/structure/closet/crate/medical containername = "stasis bag crate" group = "Medical" @@ -101,7 +121,7 @@ /obj/item/storage/box/masks, /obj/item/storage/box/gloves, ) - cost = 30 + cost = 25 containertype = /obj/structure/closet/crate/secure/surgery containername = "surgery crate" access = ACCESS_MARINE_MEDBAY diff --git a/code/game/machinery/vending/cm_vending.dm b/code/game/machinery/vending/cm_vending.dm index ff218dac89c0..6c55ce8c7946 100644 --- a/code/game/machinery/vending/cm_vending.dm +++ b/code/game/machinery/vending/cm_vending.dm @@ -46,7 +46,11 @@ /// Direction to adjacent user from which we're allowed to do offset vending var/list/vend_dir_whitelist + /// The actual inventory for this vendor as a list of lists + /// 1: name 2: amount 3: type 4: flag var/list/listed_products = list() + /// Partial stacks to hold on to as an associated list of type : amount + var/list/partial_product_stacks = list() // Are points associated with this vendor tied to its instance? var/instanced_vendor_points = FALSE @@ -900,9 +904,17 @@ GLOBAL_LIST_EMPTY(vending_products) ///this here is made to provide ability to restock vendors with different subtypes of same object, like handmade and manually filled ammo boxes. var/list/corresponding_types_list - ///If using [VEND_STOCK_DYNAMIC], assoc list of product entry to list of (product multiplier, awarded objects) - as seen in [/obj/structure/machinery/cm_vending/sorted/proc/populate_product_list] - ///This allows us to backtrack and refill the stocks when new players latejoin + /** + * If using [VEND_STOCK_DYNAMIC], assoc list of product entry to list of (1.0 scale product multiplier, awarded objects) - as seen in [/obj/structure/machinery/cm_vending/sorted/proc/populate_product_list] + * This allows us to backtrack and refill the stocks when new players latejoin. + * + * If NOT using [VEND_STOCK_DYNAMIC], assoc list of product entry to list of (estimated 1.0 scale product multiplier, scaled product multiplier) - as seen in [/obj/structure/machinery/cm_vending/sorted/proc/populate_product_list] + * This allows us to know the original amounts to know if the vendor is full of an item. + * The 1.0 scale is estimated because it is a divided by the scale rather than repopulating the list at 1.0 scale - anything that is a fixed amount won't necessarily be correct. + */ var/list/list/dynamic_stock_multipliers + ///indicates someone is performing a restock that isn't instant + var/being_restocked = FALSE /obj/structure/machinery/cm_vending/sorted/Initialize() . = ..() @@ -917,22 +929,33 @@ GLOBAL_LIST_EMPTY(vending_products) ///this proc, well, populates product list based on roundstart amount of players /obj/structure/machinery/cm_vending/sorted/proc/populate_product_list_and_boxes(scale) + dynamic_stock_multipliers = list() if(vend_flags & VEND_STOCK_DYNAMIC) populate_product_list(1.0) - dynamic_stock_multipliers = list() for(var/list/vendspec in listed_products) var/multiplier = vendspec[2] if(multiplier > 0) - var/awarded = round(vendspec[2] * scale, 1) // Starting amount + var/awarded = round(multiplier * scale, 1) // Starting amount //Record the multiplier and how many have actually been given out - dynamic_stock_multipliers[vendspec] = list(vendspec[2], awarded) + dynamic_stock_multipliers[vendspec] = list(multiplier, awarded) vendspec[2] = awarded // Override starting amount else populate_product_list(scale) + for(var/list/vendspec in listed_products) + var/amount = vendspec[2] + if(amount > -1) + var/multiplier = ceil(amount / scale) + //Record the multiplier and how many have actually been given out + dynamic_stock_multipliers[vendspec] = list(multiplier, amount) if(vend_flags & VEND_LOAD_AMMO_BOXES) populate_ammo_boxes() - return + + partial_product_stacks = list() + for(var/list/vendspec in listed_products) + var/current_type = vendspec[3] + if(ispath(current_type, /obj/item/stack)) + partial_product_stacks[current_type] = 0 ///Updates the vendor stock when the [/datum/game_mode/var/marine_tally] has changed and we're using [VEND_STOCK_DYNAMIC] ///Assumes the scale can only increase!!! Don't take their items away! @@ -979,42 +1002,78 @@ GLOBAL_LIST_EMPTY(vending_products) .["displayed_categories"] = vendor_user_inventory_list(user, null, 4) /obj/structure/machinery/cm_vending/sorted/MouseDrop_T(atom/movable/A, mob/user) - if(inoperable()) return - if(user.stat || user.is_mob_restrained()) return - if(get_dist(user, src) > 1 || get_dist(src, A) > 1) return + if(!ishuman(user)) + return + + // Try to bulk restock using a container + if(istype(A, /obj/item/storage)) + var/obj/item/storage/container = A + if(!length(container.contents)) + return + if(being_restocked) + to_chat(user, SPAN_WARNING("[src] is already being restocked, you will get in the way!")) + return + + user.visible_message(SPAN_NOTICE("[user] starts stocking a bunch of supplies into [src]."), \ + SPAN_NOTICE("You start stocking a bunch of supplies into [src].")) + being_restocked = TRUE + + for(var/obj/item/item in container.contents) + if(!do_after(user, 1 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC, src)) + being_restocked = FALSE + user.visible_message(SPAN_NOTICE("[user] stopped stocking [src] with supplies."), \ + SPAN_NOTICE("You stop stocking [src] with supplies.")) + return + if(QDELETED(item) || item.loc != container) + being_restocked = FALSE + user.visible_message(SPAN_NOTICE("[user] stopped stocking [src] with supplies."), \ + SPAN_NOTICE("You stop stocking [src] with supplies.")) + return + stock(item, user) + + being_restocked = FALSE + user.visible_message(SPAN_NOTICE("[user] finishes stocking [src] with supplies."), \ + SPAN_NOTICE("You finish stocking [src] with supplies.")) + return if(istype(A, /obj/item)) - var/obj/item/I = A - stock(I, user) + stock(A, user) /obj/structure/machinery/cm_vending/sorted/proc/stock(obj/item/item_to_stock, mob/user) - var/list/R + if(istype(item_to_stock, /obj/item/storage)) + return FALSE + var/list/stock_listed_products = get_listed_products(user) - for(R in (stock_listed_products)) - if(item_to_stock.type == R[3] && !istype(item_to_stock,/obj/item/storage)) + for(var/list/vendspec as anything in stock_listed_products) + if(item_to_stock.type == vendspec[3]) + var/partial_stacks = 0 if(istype(item_to_stock, /obj/item/device/defibrillator)) - var/obj/item/device/defibrillator/D = item_to_stock - if(!D.dcell) - to_chat(user, SPAN_WARNING("\The [item_to_stock] needs a cell in it to be restocked!")) - return - if(D.dcell.charge < D.dcell.maxcharge) - to_chat(user, SPAN_WARNING("\The [item_to_stock] needs to be fully charged to restock it!")) - return + var/obj/item/device/defibrillator/defib = item_to_stock + if(!defib.dcell) + to_chat(user, SPAN_WARNING("[item_to_stock] needs a cell in it to be restocked!")) + return FALSE + if(defib.dcell.charge < defib.dcell.maxcharge) + to_chat(user, SPAN_WARNING("[item_to_stock] needs to be fully charged to restock it!")) + return FALSE else if(istype(item_to_stock, /obj/item/cell)) - var/obj/item/cell/C = item_to_stock - if(C.charge < C.maxcharge) - to_chat(user, SPAN_WARNING("\The [item_to_stock] needs to be fully charged to restock it!")) - return + var/obj/item/cell/cell = item_to_stock + if(cell.charge < cell.maxcharge) + to_chat(user, SPAN_WARNING("[item_to_stock] needs to be fully charged to restock it!")) + return FALSE - else if(!additional_restock_checks(item_to_stock, user)) + else if(istype(item_to_stock, /obj/item/stack)) + var/obj/item/stack/item_stack = item_to_stock + partial_stacks = item_stack.amount % item_stack.max_amount + + if(!additional_restock_checks(item_to_stock, user, vendspec)) // the error message needs to go in the proc return FALSE @@ -1024,19 +1083,44 @@ GLOBAL_LIST_EMPTY(vending_products) user.temp_drop_inv_item(item_to_stock) if(isstorage(item_to_stock.loc)) //inside a storage item - var/obj/item/storage/S = item_to_stock.loc - S.remove_from_storage(item_to_stock, user.loc) + var/obj/item/storage/container = item_to_stock.loc + container.remove_from_storage(item_to_stock, user.loc) qdel(item_to_stock) - user.visible_message(SPAN_NOTICE("[user] stocks [src] with \a [R[1]]."), - SPAN_NOTICE("You stock [src] with \a [R[1]].")) - R[2]++ - update_derived_ammo_and_boxes_on_add(R) + user.visible_message(SPAN_NOTICE("[user] stocks [src] with \a [vendspec[1]]."), \ + SPAN_NOTICE("You stock [src] with \a [vendspec[1]].")) + if(partial_stacks) + var/obj/item/stack/item_stack = item_to_stock + var/existing_stacks = partial_product_stacks[item_to_stock.type] + var/combined_stacks = existing_stacks + partial_stacks + if(existing_stacks == 0 || combined_stacks > item_stack.max_amount) + vendspec[2]++ + partial_product_stacks[item_to_stock.type] = combined_stacks % item_stack.max_amount + else + vendspec[2]++ + update_derived_ammo_and_boxes_on_add(vendspec) updateUsrDialog() - return //We found our item, no reason to go on. + return TRUE //We found our item, no reason to go on. + + return FALSE /// additional restocking checks for individual vendor subtypes. Parse in item, do checks, return FALSE to fail. Include error message. -/obj/structure/machinery/cm_vending/sorted/proc/additional_restock_checks(obj/item/item_to_stock, mob/user) +/obj/structure/machinery/cm_vending/sorted/proc/additional_restock_checks(obj/item/item_to_stock, mob/user, list/vendspec) + var/dynamic_metadata = dynamic_stock_multipliers[vendspec] + if(dynamic_metadata) + if(vendspec[2] >= dynamic_metadata[2]) + if(!istype(item_to_stock, /obj/item/stack)) + to_chat(user, SPAN_WARNING("[src] is already full of [vendspec[1]]!")) + return FALSE + var/obj/item/stack/item_stack = item_to_stock + if(partial_product_stacks[item_to_stock.type] == 0) + to_chat(user, SPAN_WARNING("[src] is already full of [vendspec[1]]!")) + return FALSE // No partial stack to fill + if((partial_product_stacks[item_to_stock.type] + item_stack.amount) > item_stack.max_amount) + to_chat(user, SPAN_WARNING("[src] is already full of [vendspec[1]]!")) + return FALSE // Exceeds partial stack to fill + else + stack_trace("[src] could not find dynamic_stock_multipliers for [vendspec[1]]!") return TRUE //sending an /empty ammo box type path here will return corresponding regular (full) type of this box @@ -1243,14 +1327,20 @@ GLOBAL_LIST_INIT(cm_vending_gear_corresponding_types_list, list( /obj/structure/machinery/cm_vending/proc/vendor_inventory_ui_data(mob/user) . = list() - var/list/ui_listed_products = get_listed_products(user) - var/list/ui_categories = list() - - for (var/i in 1 to length(ui_listed_products)) - var/list/myprod = ui_listed_products[i] //we take one list from listed_products - var/p_amount = myprod[2] //amount left - ui_categories += list(p_amount) - .["stock_listing"] = ui_categories + var/list/products = get_listed_products(user) + var/list/product_amounts = list() + var/list/product_partials = list() + + for(var/i in 1 to length(products)) + var/list/cur_prod = products[i] //we take one list from listed_products + product_amounts += list(cur_prod[2]) //amount left + var/cur_type = cur_prod[3] + var/cur_amount_partial = 0 + if(cur_type in partial_product_stacks) + cur_amount_partial = partial_product_stacks[cur_type] + product_partials += list(cur_amount_partial) + .["stock_listing"] = product_amounts + .["stock_listing_partials"] = product_partials /obj/structure/machinery/cm_vending/proc/vendor_successful_vend(list/itemspec, mob/living/carbon/human/user) if(stat & IN_USE) @@ -1267,19 +1357,23 @@ GLOBAL_LIST_INIT(cm_vending_gear_corresponding_types_list, list( sleep(vend_delay) var/prod_type = itemspec[3] + var/stack_amount = 0 + if(vend_flags & VEND_LIMITED_INVENTORY) + itemspec[2]-- + if(itemspec[2] == 0) + stack_amount = partial_product_stacks[prod_type] + partial_product_stacks[prod_type] = 0 + if(vend_flags & VEND_LOAD_AMMO_BOXES) + update_derived_ammo_and_boxes(itemspec) + if(islist(prod_type)) for(var/each_type in prod_type) - vendor_successful_vend_one(each_type, user, target_turf, itemspec[4] == MARINE_CAN_BUY_UNIFORM) + vendor_successful_vend_one(each_type, user, target_turf, itemspec[4] == MARINE_CAN_BUY_UNIFORM, stack_amount) SEND_SIGNAL(src, COMSIG_VENDOR_SUCCESSFUL_VEND, src, itemspec, user) else - vendor_successful_vend_one(prod_type, user, target_turf, itemspec[4] == MARINE_CAN_BUY_UNIFORM) + vendor_successful_vend_one(prod_type, user, target_turf, itemspec[4] == MARINE_CAN_BUY_UNIFORM, stack_amount) SEND_SIGNAL(src, COMSIG_VENDOR_SUCCESSFUL_VEND, src, itemspec, user) - if(vend_flags & VEND_LIMITED_INVENTORY) - itemspec[2]-- - if(vend_flags & VEND_LOAD_AMMO_BOXES) - update_derived_ammo_and_boxes(itemspec) - else to_chat(user, SPAN_WARNING("ERROR: itemspec is missing. Please report this to admins.")) sleep(15) @@ -1288,7 +1382,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_corresponding_types_list, list( icon_state = initial(icon_state) update_icon() -/obj/structure/machinery/cm_vending/proc/vendor_successful_vend_one(prod_type, mob/living/carbon/human/user, turf/target_turf, insignas_override) +/obj/structure/machinery/cm_vending/proc/vendor_successful_vend_one(prod_type, mob/living/carbon/human/user, turf/target_turf, insignas_override, stack_amount) var/obj/item/new_item if(ispath(prod_type, /obj/item)) if(ispath(prod_type, /obj/item/weapon/gun)) @@ -1298,7 +1392,11 @@ GLOBAL_LIST_INIT(cm_vending_gear_corresponding_types_list, list( prod_type = headset_type else if(prod_type == /obj/item/clothing/gloves/marine) prod_type = gloves_type - new_item = new prod_type(target_turf) + if(stack_amount > 0 && ispath(prod_type, /obj/item/stack)) + new_item = new prod_type(target_turf, stack_amount) + else + new_item = new prod_type(target_turf) + new_item.add_fingerprint(user) else new_item = new prod_type(target_turf) @@ -1354,17 +1452,22 @@ GLOBAL_LIST_INIT(cm_vending_gear_corresponding_types_list, list( while(i <= length(products)) sleep(0.5) var/list/itemspec = products[i] + var/itemspec_item = itemspec[3] if(!itemspec[2] || itemspec[2] <= 0) i++ continue - itemspec[2] -= 1 + itemspec[2]-- var/list/spawned = list() - if(islist(itemspec[3])) - for(var/path in itemspec[3]) + if(islist(itemspec_item)) + for(var/path in itemspec_item) spawned += new path(loc) - else if(itemspec[3]) - var/path = itemspec[3] - spawned += new path(loc) + else if(itemspec_item) + if(itemspec[2] == 0 && partial_product_stacks[itemspec_item] > 0 && ispath(itemspec_item, /obj/item/stack)) + var/stack_amount = partial_product_stacks[itemspec_item] + partial_product_stacks[itemspec_item] = 0 + spawned += new itemspec_item(loc, stack_amount) + else + spawned += new itemspec_item(loc) if(throw_objects) for(var/atom/movable/spawned_atom in spawned) INVOKE_ASYNC(spawned_atom, TYPE_PROC_REF(/atom/movable, throw_atom), pick(orange(src, 4)), 4, SPEED_FAST) diff --git a/code/game/machinery/vending/vendor_types/medical.dm b/code/game/machinery/vending/vendor_types/medical.dm index 84528b44fa18..ab1df0b2abb7 100644 --- a/code/game/machinery/vending/vendor_types/medical.dm +++ b/code/game/machinery/vending/vendor_types/medical.dm @@ -1,4 +1,4 @@ -//------------SUPPLY LINK FOR MEDICAL VENDORS--------------- +//------------SUPPLY LINK FOR MEDICAL VENDORS------------ /obj/structure/medical_supply_link name = "medilink supply port" @@ -6,12 +6,13 @@ icon = 'icons/effects/warning_stripes.dmi' icon_state = "medlink_unclamped" var/base_state = "medlink" + plane = FLOOR_PLANE + layer = ABOVE_TURF_LAYER //It's the floor, man + anchored = TRUE density = FALSE unslashable = TRUE unacidable = TRUE - plane = FLOOR_PLANE - layer = ABOVE_TURF_LAYER //It's the floor, man /obj/structure/medical_supply_link/ex_act(severity, direction) return FALSE @@ -43,16 +44,178 @@ else icon_state = "[base_state]_unclamped" -// --- Green /obj/structure/medical_supply_link/green icon_state = "medlink_green_unclamped" base_state = "medlink_green" -//------------SORTED MEDICAL VENDORS--------------- + +//------------RESTOCK CARTS FOR MEDICAL VENDORS------------ + +/obj/structure/restock_cart + name = "restock cart" + desc = "A rather heavy cart filled with various supplies to restock a vendor with." + icon = 'icons/obj/objects.dmi' + icon_state = "tank_normal" // Temporary + var/overlay_color = rgb(252, 186, 3) // Temporary + + density = TRUE + anchored = FALSE + drag_delay = 2 + health = 100 // Can be destroyed in 2-4 slashes. + unslashable = FALSE + + ///The quantity of things this can restock + var/supplies_remaining = 20 + ///The max quantity of things this can restock + var/supplies_max = 20 + ///The descriptor for the kind of things being restocked + var/supply_descriptor = "supplies" + ///The sound to play when attacked + var/attacked_sound = 'sound/effects/metalhit.ogg' + ///The sound to play when destroyed + var/destroyed_sound = 'sound/effects/metalhit.ogg' + ///Random loot to spawn if destroyed as assoc list of type_path = max_quantity + var/list/destroyed_loot = list( + /obj/item/stack/sheet/metal = 2 + ) + +/obj/structure/restock_cart/medical + name = "\improper Wey-Yu restock cart" + desc = "A rather heavy cart filled with various supplies to restock a vendor with. Provided by Wey-Yu Pharmaceuticals Division(TM)." + icon = 'icons/obj/objects.dmi' + icon_state = "tank_normal" // Temporary + + supplies_remaining = 20 + supplies_max = 20 + supply_descriptor = "sets of medical supplies" + destroyed_loot = list( + /obj/item/stack/medical/advanced/ointment = 3, + /obj/item/stack/medical/advanced/bruise_pack = 2, + /obj/item/stack/medical/ointment = 3, + /obj/item/stack/medical/bruise_pack = 2, + /obj/item/stack/medical/splint = 2, + /obj/item/device/healthanalyzer = 1, + ) + +/obj/structure/restock_cart/medical/reagent + name = "\improper Wey-Yu reagent restock cart" + desc = "A rather heavy cart filled with various reagents to restock a vendor with. Provided by Wey-Yu Pharmaceuticals Division(TM)." + icon_state = "tank_normal" // Temporary + overlay_color = rgb(252, 115, 3) // Temporary + + supplies_remaining = 1200 + supplies_max = 1200 + supply_descriptor = "units of medical reagents" + destroyed_sound = 'sound/effects/slosh.ogg' + destroyed_loot = list() + +/obj/structure/restock_cart/Initialize(mapload, ...) + . = ..() + supplies_remaining = min(supplies_remaining, supplies_max) + update_icon() + +/obj/structure/restock_cart/update_icon() + . = ..() + var/image/overlay_image = image(icon, icon_state = "tn_color") // Temporary + overlay_image.color = overlay_color + overlays += overlay_image + +/obj/structure/restock_cart/get_examine_text(mob/user) + . = ..() + if(get_dist(user, src) > 2 && user != loc) + return + . += SPAN_NOTICE("It contains:") + if(supplies_remaining) + . += SPAN_NOTICE(" [supplies_remaining] [supply_descriptor].") + else + . += SPAN_NOTICE(" Nothing.") + +/obj/structure/restock_cart/deconstruct(disassembled) + if(!disassembled) + playsound(loc, destroyed_sound, 35, 1) + visible_message(SPAN_NOTICE("[src] falls apart as its contents spill everywhere!")) + + // Assumption: supplies_max is > 0 + if(supplies_remaining > 0 && length(destroyed_loot)) + var/spawned_any = FALSE + var/probability = (supplies_remaining / supplies_max) * 100 + for(var/type_path in destroyed_loot) + if(prob(probability)) + for(var/amount in 1 to rand(1, destroyed_loot[type_path])) + new type_path(loc) + spawned_any = TRUE + if(!spawned_any) // It wasn't empty so atleast drop something + var/type_path = pick(destroyed_loot) + for(var/amount in 1 to rand(1, destroyed_loot[type_path])) + new type_path(loc) + + return ..() + +/obj/structure/restock_cart/attackby(obj/item/W, mob/user) + if(HAS_TRAIT(W, TRAIT_TOOL_WRENCH)) + if(user.action_busy) + return + playsound(src, 'sound/items/Ratchet.ogg', 25, 1) + user.visible_message(SPAN_NOTICE("[user] starts to deconstruct [src]."), \ + SPAN_NOTICE("You start deconstructing [src].")) + if(!do_after(user, 5 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD, src)) + return + user.visible_message(SPAN_NOTICE("[user] deconstructs [src]."), \ + SPAN_NOTICE("You deconstruct [src].")) + playsound(src, 'sound/items/Crowbar.ogg', 25, 1) + new /obj/item/stack/sheet/metal(loc) + if(supplies_remaining) + msg_admin_niche("[key_name(user)] deconstructed [src] with [supplies_remaining] [supply_descriptor] remaining in [get_area(src)] [ADMIN_JMP(loc)]", loc.x, loc.y, loc.z) + deconstruct(TRUE) + return + + return ..() + +/obj/structure/restock_cart/proc/healthcheck(mob/user) + if(health <= 0) + if(supplies_remaining && ishuman(user)) + msg_admin_niche("[key_name(user)] destroyed [src] with [supplies_remaining] [supply_descriptor] remaining in [get_area(src)] [ADMIN_JMP(loc)]", loc.x, loc.y, loc.z) + deconstruct(FALSE) + +/obj/structure/restock_cart/bullet_act(obj/projectile/Proj) + health -= Proj.damage + playsound(src, attacked_sound, 25, 1) + healthcheck(Proj.firer) + return TRUE + +/obj/structure/restock_cart/attack_alien(mob/living/carbon/xenomorph/user) + if(unslashable) + return XENO_NO_DELAY_ACTION + user.animation_attack_on(src) + health -= (rand(user.melee_damage_lower, user.melee_damage_upper)) + playsound(src, attacked_sound, 25, 1) + user.visible_message(SPAN_DANGER("[user] slashes [src]!"), \ + SPAN_DANGER("You slash [src]!"), null, 5, CHAT_TYPE_XENO_COMBAT) + healthcheck(user) + return XENO_ATTACK_ACTION + +/obj/structure/restock_cart/ex_act(severity) + if(indestructible) + return + + switch(severity) + if(0 to EXPLOSION_THRESHOLD_LOW) + if(prob(5)) + deconstruct(FALSE) + return + if(EXPLOSION_THRESHOLD_LOW to EXPLOSION_THRESHOLD_MEDIUM) + if(prob(50)) + deconstruct(FALSE) + return + if(EXPLOSION_THRESHOLD_MEDIUM to INFINITY) + deconstruct(FALSE) + return + +//------------SORTED MEDICAL VENDORS------------ /obj/structure/machinery/cm_vending/sorted/medical name = "\improper Wey-Med Plus" - desc = "Medical Pharmaceutical dispenser. Provided by Wey-Yu Pharmaceuticals Division(TM)." + desc = "Medical pharmaceutical dispenser. Provided by Wey-Yu Pharmaceuticals Division(TM)." icon_state = "med" req_access = list(ACCESS_MARINE_MEDBAY) @@ -64,12 +227,18 @@ vendor_theme = VENDOR_THEME_COMPANY vend_delay = 0.5 SECONDS - /// sets vendor to require a medlink to be able to resupply - var/requires_supply_link_port = TRUE + /// Whether the vendor can use a medlink to be able to resupply automatically + var/allow_supply_link_restock = TRUE + /// Whether this vendor supports health scanning the user via mouse drop + var/healthscan = TRUE var/datum/health_scan/last_health_display - var/healthscan = TRUE + /// The starting volume of the chem refill tank + var/chem_refill_volume = 600 + /// The maximum volume of the chem refill tank + var/chem_refill_volume_max = 600 + /// A list of item types that allow reagent refilling var/list/chem_refill = list( /obj/item/reagent_container/hypospray/autoinjector/bicaridine, /obj/item/reagent_container/hypospray/autoinjector/dexalinp, @@ -79,6 +248,7 @@ /obj/item/reagent_container/hypospray/autoinjector/oxycodone, /obj/item/reagent_container/hypospray/autoinjector/tramadol, /obj/item/reagent_container/hypospray/autoinjector/tricord, + /obj/item/reagent_container/hypospray/autoinjector/skillless, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, @@ -95,14 +265,7 @@ /obj/item/reagent_container/glass/bottle/oxycodone, /obj/item/reagent_container/glass/bottle/peridaxon, /obj/item/reagent_container/glass/bottle/tramadol, - ) - var/list/stack_refill = list( - /obj/item/stack/medical/advanced/ointment, - /obj/item/stack/medical/advanced/bruise_pack, - /obj/item/stack/medical/ointment, - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/splint - ) + ) /obj/structure/machinery/cm_vending/sorted/medical/Destroy() QDEL_NULL(last_health_display) @@ -111,7 +274,13 @@ /obj/structure/machinery/cm_vending/sorted/medical/get_examine_text(mob/living/carbon/human/user) . = ..() if(healthscan) - . += SPAN_NOTICE("The [src.name] offers assisted medical scan, for ease of usage with minimal training. Present the target in front of the scanner to scan.") + . += SPAN_NOTICE("[src] offers assisted medical scans, for ease of use with minimal training. Present the target in front of the scanner to scan.") + +/obj/structure/machinery/cm_vending/sorted/medical/ui_data(mob/user) + . = ..() + if(LAZYLEN(chem_refill)) + .["reagents"] = chem_refill_volume + .["reagents_max"] = chem_refill_volume_max /// checks if there is a supply link in our location and we are anchored to it /obj/structure/machinery/cm_vending/sorted/medical/proc/get_supply_link() @@ -122,29 +291,105 @@ return FALSE return TRUE -/obj/structure/machinery/cm_vending/sorted/medical/additional_restock_checks(obj/item/item_to_stock, mob/user) - if(istype(item_to_stock, /obj/item/reagent_container/hypospray/autoinjector) || istype(item_to_stock, /obj/item/reagent_container/glass/bottle)) - if(requires_supply_link_port && !get_supply_link()) - var/obj/item/reagent_container/container = item_to_stock - if(container.reagents.total_volume < container.reagents.maximum_volume) - if(user) - to_chat(user, SPAN_WARNING("[src] makes a buzzing noise as it rejects [container.name]. Looks like this vendor cannot refill these without a connected supply link.")) - playsound(src, 'sound/machines/buzz-sigh.ogg', 15, TRUE) +/obj/structure/machinery/cm_vending/sorted/medical/additional_restock_checks(obj/item/item_to_stock, mob/user, list/vendspec) + var/dynamic_metadata = dynamic_stock_multipliers[vendspec] + if(dynamic_metadata) + if(vendspec[2] >= dynamic_metadata[2] && (!allow_supply_link_restock || !get_supply_link())) + if(!istype(item_to_stock, /obj/item/stack)) + to_chat(user, SPAN_WARNING("[src] is already full of [vendspec[1]]!")) return FALSE + var/obj/item/stack/item_stack = item_to_stock + if(partial_product_stacks[item_to_stock.type] == 0) + to_chat(user, SPAN_WARNING("[src] is already full of [vendspec[1]]!")) + return FALSE // No partial stack to fill + if((partial_product_stacks[item_to_stock.type] + item_stack.amount) > item_stack.max_amount) + to_chat(user, SPAN_WARNING("[src] is already full of [vendspec[1]]!")) + return FALSE // Exceeds partial stack to fill + else + stack_trace("[src] could not find dynamic_stock_multipliers for [vendspec[1]]!") - //stacked items handling if the vendor cannot restock partial stacks - else if(istype(item_to_stock, /obj/item/stack)) - if(requires_supply_link_port && !get_supply_link()) - var/obj/item/stack/restock_stack = item_to_stock - if(restock_stack.amount < restock_stack.max_amount) // if the stack is not full - if(user) - to_chat(user, SPAN_WARNING("[src] makes a buzzing noise as it rejects [restock_stack]. Looks like this vendor cannot restock these without a connected supply link.")) - playsound(src, 'sound/machines/buzz-sigh.ogg', 15, TRUE) + if(istype(item_to_stock, /obj/item/reagent_container)) + if(istype(item_to_stock, /obj/item/reagent_container/syringe) || istype(item_to_stock, /obj/item/reagent_container/dropper)) + var/obj/item/reagent_container/container = item_to_stock + if(container.reagents.total_volume != 0) + to_chat(user, SPAN_WARNING("[item_to_stock] needs to be empty to restock it!")) return FALSE + else + return try_deduct_chem(item_to_stock, user) + return TRUE +/// Attempts to consume our reagents needed for the container (doesn't actually change the container) +/// Will return TRUE if reagents were deducated or no reagents were needed +/obj/structure/machinery/cm_vending/sorted/medical/proc/try_deduct_chem(obj/item/reagent_container/container, mob/user) + var/missing_reagents = container.reagents.maximum_volume - container.reagents.total_volume + if(missing_reagents <= 0) + return TRUE + if(!LAZYLEN(chem_refill) || !(container.type in chem_refill)) + if(container.reagents.total_volume == initial(container.reagents.total_volume)) + return TRUE + to_chat(user, SPAN_WARNING("[src] cannot refill [container].")) + return FALSE + if(chem_refill_volume < missing_reagents) + var/auto_refill = allow_supply_link_restock && get_supply_link() + to_chat(user, SPAN_WARNING("[src] blinks red and makes a buzzing noise as it rejects [container]. Looks like it doesn't have enough reagents [auto_refill ? "yet" : "left"].")) + playsound(src, 'sound/machines/buzz-sigh.ogg', 15, TRUE) + return FALSE + + chem_refill_volume -= missing_reagents + to_chat(user, SPAN_NOTICE("[src] makes a whirring noise as it refills your [container.name].")) + playsound(src, 'sound/effects/refill.ogg', 10, 1, 3) + return TRUE + +/// Performs automatic restocking via medical cart - will set being_restocked true during the action +/obj/structure/machinery/cm_vending/sorted/medical/proc/cart_restock(obj/structure/restock_cart/medical/cart, mob/user) + if(cart.supplies_remaining <= 0) + to_chat(user, SPAN_WARNING("[cart] is empty!")) + return + if(being_restocked) + to_chat(user, SPAN_WARNING("[src] is already being restocked, you will get in the way!")) + return + + var/restocking_reagents = istype(cart, /obj/structure/restock_cart/medical/reagent) + if(restocking_reagents && !LAZYLEN(chem_refill)) + to_chat(user, SPAN_WARNING("[src] doesn't use [cart.supply_descriptor]!")) + return + + user.visible_message(SPAN_NOTICE("[user] starts stocking [cart.supply_descriptor] supplies into [src]."), \ + SPAN_NOTICE("You start stocking [cart.supply_descriptor] into [src].")) + being_restocked = TRUE + + while(cart.supplies_remaining > 0) + if(!do_after(user, 1 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC, src)) + being_restocked = FALSE + user.visible_message(SPAN_NOTICE("[user] stopped stocking [src] with [cart.supply_descriptor]."), \ + SPAN_NOTICE("You stop stocking [src] with [cart.supply_descriptor].")) + return + if(QDELETED(cart) || get_dist(user, cart) > 1) + being_restocked = FALSE + user.visible_message(SPAN_NOTICE("[user] stopped stocking [src] with [cart.supply_descriptor]."), \ + SPAN_NOTICE("You stop stocking [src] with [cart.supply_descriptor].")) + return + + if(restocking_reagents) + var/reagent_added = restock_reagents(min(cart.supplies_remaining, 100)) + if(reagent_added <= 0 || chem_refill_volume == chem_refill_volume_max) + break // All done + cart.supplies_remaining -= reagent_added + else + if(!restock_supplies(prob_to_skip = 0, can_remove = FALSE)) + break // All done + cart.supplies_remaining-- + + being_restocked = FALSE + user.visible_message(SPAN_NOTICE("[user] finishes stocking [src] with [cart.supply_descriptor]."), \ + SPAN_NOTICE("You finish stocking [src] with [cart.supply_descriptor].")) + /obj/structure/machinery/cm_vending/sorted/medical/attackby(obj/item/I, mob/user) - if(stat == WORKING && LAZYLEN(chem_refill) && (istype(I, /obj/item/reagent_container/hypospray/autoinjector) || istype(I, /obj/item/reagent_container/glass/bottle))) // only if we are completely fine and working + if(stat != WORKING) + return ..() + + if(istype(I, /obj/item/reagent_container)) if(!hacked) if(!allowed(user)) to_chat(user, SPAN_WARNING("Access denied.")) @@ -154,54 +399,37 @@ to_chat(user, SPAN_WARNING("This machine isn't for you.")) return - var/obj/item/reagent_container/C = I - if(!(C.type in chem_refill)) - to_chat(user, SPAN_WARNING("[src] cannot refill the [C.name].")) + var/obj/item/reagent_container/container = I + if(istype(I, /obj/item/reagent_container/syringe) || istype(I, /obj/item/reagent_container/dropper)) + if(!stock(container, user)) + return ..() return - if(C.reagents.total_volume == C.reagents.maximum_volume) - to_chat(user, SPAN_WARNING("[src] makes a warning noise. The [C.name] is currently full.")) + if(container.reagents.total_volume == container.reagents.maximum_volume) + if(!stock(container, user)) + return ..() return - if(requires_supply_link_port && !get_supply_link()) - to_chat(user, SPAN_WARNING("[src] makes a buzzing noise as it rejects [C.name]. Looks like this vendor cannot refill these without a connected supply link.")) - playsound(src, 'sound/machines/buzz-sigh.ogg', 15, TRUE) - return + if(!try_deduct_chem(container, user)) + return ..() - to_chat(user, SPAN_NOTICE("[src] makes a whirring noise as it refills your [C.name].")) // Since the reagent is deleted on use it's easier to make a new one instead of snowflake checking - var/obj/item/reagent_container/new_container = new C.type(src) - qdel(C) + var/obj/item/reagent_container/new_container = new container.type(src) + qdel(container) user.put_in_hands(new_container) - else if(stat == WORKING && LAZYLEN(stack_refill) && (istype(I, /obj/item/stack))) - if(!hacked) - if(!allowed(user)) - to_chat(user, SPAN_WARNING("Access denied.")) - return - - if(LAZYLEN(vendor_role) && !vendor_role.Find(user.job)) - to_chat(user, SPAN_WARNING("This machine isn't for you.")) - return - - var/obj/item/stack/S = I - if(!(S.type in stack_refill)) - to_chat(user, SPAN_WARNING("[src] cannot restock the [S.name].")) - return + return - if(S.amount == S.max_amount) - to_chat(user, SPAN_WARNING("[src] makes a warning noise. The [S.name] is currently fully stacked.")) + if(ishuman(user) && istype(I, /obj/item/grab)) + var/obj/item/grab/grabbed = I + if(istype(grabbed.grabbed_thing, /obj/structure/restock_cart/medical)) + cart_restock(grabbed.grabbed_thing, user) return - if(requires_supply_link_port && !get_supply_link()) - to_chat(user, SPAN_WARNING("[src] makes a buzzing noise as it rejects [S.name]. Looks like this vendor cannot restock these without a connected supply link.")) - playsound(src, 'sound/machines/buzz-sigh.ogg', 15, TRUE) + if(hacked || (allowed(user) && (!LAZYLEN(vendor_role) || vendor_role.Find(user.job)))) + if(stock(I, user)) return - to_chat(user, SPAN_NOTICE("[src] makes a whirring noise as it restocks your [S.name].")) - S.amount = S.max_amount - S.update_icon() - else - . = ..() + return ..() /obj/structure/machinery/cm_vending/sorted/medical/MouseDrop(obj/over_object as obj) if(stat == WORKING && over_object == usr && CAN_PICKUP(usr, src)) @@ -211,7 +439,7 @@ return if(!healthscan) - to_chat(user, SPAN_WARNING("\The [src] does not have health scanning function.")) + to_chat(user, SPAN_WARNING("[src] does not have health scanning function.")) return if (!last_health_display) @@ -222,63 +450,182 @@ last_health_display.look_at(user, DETAIL_LEVEL_HEALTHANALYSER, bypass_checks = TRUE) return +/obj/structure/machinery/cm_vending/sorted/medical/MouseDrop_T(atom/movable/A, mob/user) + if(inoperable()) + return + if(user.stat || user.is_mob_restrained()) + return + if(get_dist(user, src) > 1 || get_dist(user, A) > 1) // More lenient + return + if(!ishuman(user)) + return + + if(istype(A, /obj/structure/restock_cart/medical)) + cart_restock(A, user) + return + + return ..() + /obj/structure/machinery/cm_vending/sorted/medical/populate_product_list(scale) listed_products = list( list("FIELD SUPPLIES", -1, null, null), - list("Burn Kit", floor(scale * 6), /obj/item/stack/medical/advanced/ointment, VENDOR_ITEM_REGULAR), - list("Trauma Kit", floor(scale * 6), /obj/item/stack/medical/advanced/bruise_pack, VENDOR_ITEM_REGULAR), - list("Ointment", floor(scale * 6), /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), - list("Roll of Gauze", floor(scale * 6), /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), - list("Splints", floor(scale * 6), /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR), + list("Burn Kit", floor(scale * 10), /obj/item/stack/medical/advanced/ointment, VENDOR_ITEM_REGULAR), + list("Trauma Kit", floor(scale * 10), /obj/item/stack/medical/advanced/bruise_pack, VENDOR_ITEM_REGULAR), + list("Ointment", floor(scale * 10), /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), + list("Roll of Gauze", floor(scale * 10), /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), + list("Splints", floor(scale * 10), /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR), list("AUTOINJECTORS", -1, null, null), - list("Autoinjector (Bicaridine)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/bicaridine, VENDOR_ITEM_REGULAR), - list("Autoinjector (Dexalin+)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/dexalinp, VENDOR_ITEM_REGULAR), - list("Autoinjector (Epinephrine)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/adrenaline, VENDOR_ITEM_REGULAR), - list("Autoinjector (Inaprovaline)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/inaprovaline, VENDOR_ITEM_REGULAR), - list("Autoinjector (Kelotane)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/kelotane, VENDOR_ITEM_REGULAR), - list("Autoinjector (Oxycodone)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/oxycodone, VENDOR_ITEM_REGULAR), - list("Autoinjector (Tramadol)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/tramadol, VENDOR_ITEM_REGULAR), - list("Autoinjector (Tricord)", floor(scale * 3), /obj/item/reagent_container/hypospray/autoinjector/tricord, VENDOR_ITEM_REGULAR), + list("Autoinjector (Bicaridine)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/bicaridine, VENDOR_ITEM_REGULAR), + list("Autoinjector (Dexalin+)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/dexalinp, VENDOR_ITEM_REGULAR), + list("Autoinjector (Epinephrine)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/adrenaline, VENDOR_ITEM_REGULAR), + list("Autoinjector (Inaprovaline)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/inaprovaline, VENDOR_ITEM_REGULAR), + list("Autoinjector (Kelotane)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/kelotane, VENDOR_ITEM_REGULAR), + list("Autoinjector (Oxycodone)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/oxycodone, VENDOR_ITEM_REGULAR), + list("Autoinjector (Tramadol)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/tramadol, VENDOR_ITEM_REGULAR), + list("Autoinjector (Tricord)", floor(scale * 5), /obj/item/reagent_container/hypospray/autoinjector/tricord, VENDOR_ITEM_REGULAR), list("LIQUID BOTTLES", -1, null, null), - list("Bottle (Bicaridine)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/bicaridine, VENDOR_ITEM_REGULAR), - list("Bottle (Dylovene)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/antitoxin, VENDOR_ITEM_REGULAR), - list("Bottle (Dexalin)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/dexalin, VENDOR_ITEM_REGULAR), - list("Bottle (Inaprovaline)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/inaprovaline, VENDOR_ITEM_REGULAR), - list("Bottle (Kelotane)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/kelotane, VENDOR_ITEM_REGULAR), - list("Bottle (Oxycodone)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/oxycodone, VENDOR_ITEM_REGULAR), - list("Bottle (Peridaxon)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/peridaxon, VENDOR_ITEM_REGULAR), - list("Bottle (Tramadol)", floor(scale * 4), /obj/item/reagent_container/glass/bottle/tramadol, VENDOR_ITEM_REGULAR), + list("Bottle (Bicaridine)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/bicaridine, VENDOR_ITEM_REGULAR), + list("Bottle (Dylovene)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/antitoxin, VENDOR_ITEM_REGULAR), + list("Bottle (Dexalin)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/dexalin, VENDOR_ITEM_REGULAR), + list("Bottle (Inaprovaline)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/inaprovaline, VENDOR_ITEM_REGULAR), + list("Bottle (Kelotane)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/kelotane, VENDOR_ITEM_REGULAR), + list("Bottle (Oxycodone)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/oxycodone, VENDOR_ITEM_REGULAR), + list("Bottle (Peridaxon)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/peridaxon, VENDOR_ITEM_REGULAR), + list("Bottle (Tramadol)", floor(scale * 3), /obj/item/reagent_container/glass/bottle/tramadol, VENDOR_ITEM_REGULAR), list("PILL BOTTLES", -1, null, null), - list("Pill Bottle (Bicaridine)", floor(scale * 2), /obj/item/storage/pill_bottle/bicaridine, VENDOR_ITEM_REGULAR), - list("Pill Bottle (Dexalin)", floor(scale * 2), /obj/item/storage/pill_bottle/dexalin, VENDOR_ITEM_REGULAR), - list("Pill Bottle (Dylovene)", floor(scale * 2), /obj/item/storage/pill_bottle/antitox, VENDOR_ITEM_REGULAR), - list("Pill Bottle (Inaprovaline)", floor(scale * 2), /obj/item/storage/pill_bottle/inaprovaline, VENDOR_ITEM_REGULAR), - list("Pill Bottle (Kelotane)", floor(scale * 2), /obj/item/storage/pill_bottle/kelotane, VENDOR_ITEM_REGULAR), - list("Pill Bottle (Peridaxon)", floor(scale * 1), /obj/item/storage/pill_bottle/peridaxon, VENDOR_ITEM_REGULAR), - list("Pill Bottle (Tramadol)", floor(scale * 2), /obj/item/storage/pill_bottle/tramadol, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Bicaridine)", floor(scale * 4), /obj/item/storage/pill_bottle/bicaridine, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Dexalin)", floor(scale * 4), /obj/item/storage/pill_bottle/dexalin, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Dylovene)", floor(scale * 4), /obj/item/storage/pill_bottle/antitox, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Inaprovaline)", floor(scale * 4), /obj/item/storage/pill_bottle/inaprovaline, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Kelotane)", floor(scale * 4), /obj/item/storage/pill_bottle/kelotane, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Peridaxon)", floor(scale * 3), /obj/item/storage/pill_bottle/peridaxon, VENDOR_ITEM_REGULAR), + list("Pill Bottle (Tramadol)", floor(scale * 4), /obj/item/storage/pill_bottle/tramadol, VENDOR_ITEM_REGULAR), list("MEDICAL UTILITIES", -1, null, null), list("Emergency Defibrillator", floor(scale * 3), /obj/item/device/defibrillator, VENDOR_ITEM_REGULAR), list("Surgical Line", floor(scale * 2), /obj/item/tool/surgery/surgical_line, VENDOR_ITEM_REGULAR), list("Synth-Graft", floor(scale * 2), /obj/item/tool/surgery/synthgraft, VENDOR_ITEM_REGULAR), - list("Hypospray", floor(scale * 2), /obj/item/reagent_container/hypospray/tricordrazine, VENDOR_ITEM_REGULAR), + list("Hypospray", floor(scale * 3), /obj/item/reagent_container/hypospray/tricordrazine, VENDOR_ITEM_REGULAR), list("Health Analyzer", floor(scale * 5), /obj/item/device/healthanalyzer, VENDOR_ITEM_REGULAR), list("M276 Pattern Medical Storage Rig", floor(scale * 2), /obj/item/storage/belt/medical, VENDOR_ITEM_REGULAR), list("Medical HUD Glasses", floor(scale * 3), /obj/item/clothing/glasses/hud/health, VENDOR_ITEM_REGULAR), - list("Stasis Bag", floor(scale * 2), /obj/item/bodybag/cryobag, VENDOR_ITEM_REGULAR), + list("Stasis Bag", floor(scale * 3), /obj/item/bodybag/cryobag, VENDOR_ITEM_REGULAR), list("Syringe", floor(scale * 7), /obj/item/reagent_container/syringe, VENDOR_ITEM_REGULAR) ) +/obj/structure/machinery/cm_vending/sorted/medical/populate_product_list_and_boxes(scale) + . = ..() + + // If this is groundside and isn't dynamically changing we will spawn with stock randomly removed from it + if(vend_flags & VEND_STOCK_DYNAMIC) + return + if(Check_WO()) + return + var/turf/location = get_turf(src) + if(location && is_ground_level(location.z)) + random_unstock() + +/obj/structure/machinery/cm_vending/sorted/medical/Initialize() + . = ..() + + // If this is a medlinked vendor (that needs a link) and isn't dynamically changing it will periodically restock itself + if(vend_flags & VEND_STOCK_DYNAMIC) + return + if(!allow_supply_link_restock) + return + if(!get_supply_link()) + return + START_PROCESSING(SSslowobj, src) + +/obj/structure/machinery/cm_vending/sorted/medical/toggle_anchored(obj/item/W, mob/user) + . = ..() + + // If the anchor state changed, this is a vendor that needs a link, and isn't dynamically changing, update whether we automatically restock + if(. && !(vend_flags & VEND_STOCK_DYNAMIC) && allow_supply_link_restock) + if(get_supply_link()) + START_PROCESSING(SSslowobj, src) + else + STOP_PROCESSING(SSslowobj, src) + +/obj/structure/machinery/cm_vending/sorted/medical/process() + if(!get_supply_link()) + STOP_PROCESSING(SSslowobj, src) + return // Somehow we lost our link + if(inoperable()) + return + if(world.time - SSticker.mode.round_time_lobby > 20 MINUTES) + restock_supplies() + restock_reagents() + +/// Randomly (based on prob_to_skip) adjusts all amounts of listed_products towards their desired values by 1 +/// Returns the quantity of items added +/obj/structure/machinery/cm_vending/sorted/medical/proc/restock_supplies(prob_to_skip = 80, can_remove = TRUE) + . = 0 + for(var/list/vendspec as anything in listed_products) + if(vendspec[2] < 0) + continue // It's a section title, not an actual entry + var/dynamic_metadata = dynamic_stock_multipliers[vendspec] + if(!dynamic_metadata) + stack_trace("[src] could not find dynamic_stock_multipliers for [vendspec[1]]!") + continue + var/cur_type = vendspec[3] + if(vendspec[2] == dynamic_metadata[2]) + if((cur_type in partial_product_stacks) && partial_product_stacks[cur_type] > 0) + partial_product_stacks[cur_type] = 0 + .++ + continue // Already at desired value + if(vendspec[2] > dynamic_metadata[2]) + if(can_remove) + vendspec[2]-- + if(cur_type in partial_product_stacks) + partial_product_stacks[cur_type] = 0 + if(vend_flags & VEND_LOAD_AMMO_BOXES) + update_derived_ammo_and_boxes(vendspec) + continue // Returned some items to the void + if(prob(prob_to_skip)) + continue // 20% chance to restock per entry by default + vendspec[2]++ + if(vend_flags & VEND_LOAD_AMMO_BOXES) + update_derived_ammo_and_boxes_on_add(vendspec) + .++ + +/// Refills reagents towards chem_refill_volume_max +/// Returns the quantity of reagents added +/obj/structure/machinery/cm_vending/sorted/medical/proc/restock_reagents(additional_volume = 125) + var/old_value = chem_refill_volume + chem_refill_volume = min(chem_refill_volume + additional_volume, chem_refill_volume_max) + return chem_refill_volume - old_value + +/// Randomly removes amounts of listed_products and reagents +/obj/structure/machinery/cm_vending/sorted/medical/proc/random_unstock() + // Random interval of 25 for reagents + chem_refill_volume = rand(0, chem_refill_volume_max * 0.04) * 25 + + for(var/list/vendspec as anything in listed_products) + var/amount = vendspec[2] + if(amount <= 0) + continue + + // Chance to just be empty + if(prob(25)) + vendspec[2] = 0 + continue + + // Otherwise its some amount between 1 and the original amount + vendspec[2] = rand(1, amount) + /obj/structure/machinery/cm_vending/sorted/medical/chemistry name = "\improper Wey-Chem Plus" desc = "Medical chemistry dispenser. Provided by Wey-Yu Pharmaceuticals Division(TM)." icon_state = "chem" req_access = list(ACCESS_MARINE_CHEMISTRY) - healthscan = FALSE + + chem_refill_volume = 1200 + chem_refill_volume_max = 1200 chem_refill = list( /obj/item/reagent_container/glass/bottle/bicaridine, /obj/item/reagent_container/glass/bottle/antitoxin, @@ -289,19 +636,18 @@ /obj/item/reagent_container/glass/bottle/peridaxon, /obj/item/reagent_container/glass/bottle/tramadol, ) - stack_refill = null /obj/structure/machinery/cm_vending/sorted/medical/chemistry/populate_product_list(scale) listed_products = list( list("LIQUID BOTTLES", -1, null, null), - list("Bicaridine Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/bicaridine, VENDOR_ITEM_REGULAR), - list("Dylovene Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/antitoxin, VENDOR_ITEM_REGULAR), - list("Dexalin Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/dexalin, VENDOR_ITEM_REGULAR), - list("Inaprovaline Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/inaprovaline, VENDOR_ITEM_REGULAR), - list("Kelotane Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/kelotane, VENDOR_ITEM_REGULAR), - list("Oxycodone Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/oxycodone, VENDOR_ITEM_REGULAR), - list("Peridaxon Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/peridaxon, VENDOR_ITEM_REGULAR), - list("Tramadol Bottle", floor(scale * 5), /obj/item/reagent_container/glass/bottle/tramadol, VENDOR_ITEM_REGULAR), + list("Bicaridine Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/bicaridine, VENDOR_ITEM_REGULAR), + list("Dylovene Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/antitoxin, VENDOR_ITEM_REGULAR), + list("Dexalin Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/dexalin, VENDOR_ITEM_REGULAR), + list("Inaprovaline Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/inaprovaline, VENDOR_ITEM_REGULAR), + list("Kelotane Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/kelotane, VENDOR_ITEM_REGULAR), + list("Oxycodone Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/oxycodone, VENDOR_ITEM_REGULAR), + list("Peridaxon Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/peridaxon, VENDOR_ITEM_REGULAR), + list("Tramadol Bottle", floor(scale * 6), /obj/item/reagent_container/glass/bottle/tramadol, VENDOR_ITEM_REGULAR), list("MISCELLANEOUS", -1, null, null), list("Beaker (60 Units)", floor(scale * 3), /obj/item/reagent_container/glass/beaker, VENDOR_ITEM_REGULAR), @@ -324,13 +670,13 @@ name = "\improper Medical Equipment Vendor" desc = "A vending machine dispensing various pieces of medical equipment." req_one_access = list(ACCESS_ILLEGAL_PIRATE, ACCESS_UPP_GENERAL, ACCESS_CLF_GENERAL) - requires_supply_link_port = FALSE req_access = null vendor_theme = VENDOR_THEME_CLF + allow_supply_link_restock = FALSE /obj/structure/machinery/cm_vending/sorted/medical/marinemed name = "\improper ColMarTech MarineMed" - desc = "Medical Pharmaceutical dispenser with basic medical supplies for marines." + desc = "Medical pharmaceutical dispenser with basic medical supplies for marines." icon_state = "marinemed" req_access = list() req_one_access = list() @@ -340,11 +686,6 @@ /obj/item/reagent_container/hypospray/autoinjector/skillless, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, ) - stack_refill = list( - /obj/item/stack/medical/ointment, - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/splint, - ) /obj/structure/machinery/cm_vending/sorted/medical/marinemed/populate_product_list(scale) listed_products = list( @@ -357,80 +698,80 @@ list("FIELD SUPPLIES", -1, null, null), list("Fire Extinguisher (portable)", 5, /obj/item/tool/extinguisher/mini, VENDOR_ITEM_REGULAR), - list("Ointment", floor(scale * 7), /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), - list("Roll of Gauze", floor(scale * 7), /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), - list("Splints", floor(scale * 7), /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR) + list("Ointment", floor(scale * 8), /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), + list("Roll of Gauze", floor(scale * 8), /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), + list("Splints", floor(scale * 8), /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR) ) /obj/structure/machinery/cm_vending/sorted/medical/marinemed/antag name = "\improper Basic Medical Supplies Vendor" desc = "A vending machine dispensing basic medical supplies." req_one_access = list(ACCESS_ILLEGAL_PIRATE, ACCESS_UPP_GENERAL, ACCESS_CLF_GENERAL) - requires_supply_link_port = FALSE req_access = null vendor_theme = VENDOR_THEME_CLF + allow_supply_link_restock = FALSE /obj/structure/machinery/cm_vending/sorted/medical/blood name = "\improper MM Blood Dispenser" - desc = "The Marine Med Brand Blood Pack Dispensary is the premier, top-of-the-line blood dispenser of 2105! Get yours today!" //Don't update this year, the joke is it's old. + desc = "The MarineMed brand blood dispensary is the premier, top-of-the-line blood dispenser of 2105! Get yours today!" //Don't update this year, the joke is it's old. icon_state = "blood" wrenchable = TRUE hackable = TRUE - - listed_products = list( - list("BLOOD PACKS", -1, null, null), - list("A+ Blood Pack", 5, /obj/item/reagent_container/blood/APlus, VENDOR_ITEM_REGULAR), - list("A- Blood Pack", 5, /obj/item/reagent_container/blood/AMinus, VENDOR_ITEM_REGULAR), - list("B+ Blood Pack", 5, /obj/item/reagent_container/blood/BPlus, VENDOR_ITEM_REGULAR), - list("B- Blood Pack", 5, /obj/item/reagent_container/blood/BMinus, VENDOR_ITEM_REGULAR), - list("O+ Blood Pack", 5, /obj/item/reagent_container/blood/OPlus, VENDOR_ITEM_REGULAR), - list("O- Blood Pack", 5, /obj/item/reagent_container/blood/OMinus, VENDOR_ITEM_REGULAR), - - list("MISCELLANEOUS", -1, null, null), - list("Empty Blood Pack", 5, /obj/item/reagent_container/blood, VENDOR_ITEM_REGULAR) - ) - healthscan = FALSE + allow_supply_link_restock = FALSE chem_refill = null - stack_refill = null /obj/structure/machinery/cm_vending/sorted/medical/blood/bolted wrenchable = FALSE /obj/structure/machinery/cm_vending/sorted/medical/blood/populate_product_list(scale) - return + listed_products = list( + list("BLOOD PACKS", -1, null, null), + list("A+ Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood/APlus, VENDOR_ITEM_REGULAR), + list("A- Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood/AMinus, VENDOR_ITEM_REGULAR), + list("B+ Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood/BPlus, VENDOR_ITEM_REGULAR), + list("B- Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood/BMinus, VENDOR_ITEM_REGULAR), + list("O+ Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood/OPlus, VENDOR_ITEM_REGULAR), + list("O- Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood/OMinus, VENDOR_ITEM_REGULAR), + + list("MISCELLANEOUS", -1, null, null), + list("Empty Blood Pack", floor(scale * 5), /obj/item/reagent_container/blood, VENDOR_ITEM_REGULAR) + ) /obj/structure/machinery/cm_vending/sorted/medical/blood/antag req_one_access = list(ACCESS_ILLEGAL_PIRATE, ACCESS_UPP_GENERAL, ACCESS_CLF_GENERAL) - requires_supply_link_port = FALSE req_access = null vendor_theme = VENDOR_THEME_CLF + allow_supply_link_restock = FALSE + + +//------------WALL MED VENDORS------------ /obj/structure/machinery/cm_vending/sorted/medical/wall_med name = "\improper NanoMed" - desc = "Wall-mounted Medical Equipment Dispenser." + desc = "A wall-mounted medical equipment dispenser." icon_state = "wallmed" - vend_delay = 0.7 SECONDS - requires_supply_link_port = FALSE - + appearance_flags = TILE_BOUND req_access = list() - density = FALSE wrenchable = FALSE + vend_delay = 0.7 SECONDS + allow_supply_link_restock = FALSE + listed_products = list( list("SUPPLIES", -1, null, null), - list("First-Aid Autoinjector", 1, /obj/item/reagent_container/hypospray/autoinjector/skillless, VENDOR_ITEM_REGULAR), - list("Pain-Stop Autoinjector", 1, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, VENDOR_ITEM_REGULAR), - list("Roll Of Gauze", 2, /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), - list("Ointment", 2, /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), - list("Medical Splints", 1, /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR), + list("First-Aid Autoinjector", 2, /obj/item/reagent_container/hypospray/autoinjector/skillless, VENDOR_ITEM_REGULAR), + list("Pain-Stop Autoinjector", 2, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, VENDOR_ITEM_REGULAR), + list("Roll Of Gauze", 4, /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), + list("Ointment", 4, /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), + list("Medical Splints", 4, /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR), list("UTILITY", -1, null, null), - list("HF2 Health Analyzer", 1, /obj/item/device/healthanalyzer, VENDOR_ITEM_REGULAR) + list("HF2 Health Analyzer", 2, /obj/item/device/healthanalyzer, VENDOR_ITEM_REGULAR) ) - appearance_flags = TILE_BOUND - + chem_refill_volume = 250 + chem_refill_volume_max = 250 chem_refill = list( /obj/item/reagent_container/hypospray/autoinjector/skillless, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, @@ -439,29 +780,27 @@ /obj/item/reagent_container/hypospray/autoinjector/kelotane/skillless, /obj/item/reagent_container/hypospray/autoinjector/tramadol/skillless, ) - stack_refill = list( - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/splint, - /obj/item/stack/medical/ointment, - ) /obj/structure/machinery/cm_vending/sorted/medical/wall_med/limited - desc = "Wall-mounted Medical Equipment Dispenser. This version is more limited than standard USCM NanoMeds." + desc = "A wall-mounted medical equipment dispenser. This version is more limited than standard USCM NanoMeds." + chem_refill_volume = 150 + chem_refill_volume_max = 150 chem_refill = list( /obj/item/reagent_container/hypospray/autoinjector/skillless, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, ) - stack_refill = list( - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/ointment, - ) /obj/structure/machinery/cm_vending/sorted/medical/wall_med/lifeboat name = "Lifeboat Medical Cabinet" icon = 'icons/obj/structures/machinery/lifeboat.dmi' icon_state = "medcab" desc = "A wall-mounted cabinet containing medical supplies vital to survival. While better equipped, it can only refill basic supplies." + unacidable = TRUE + unslashable = TRUE + wrenchable = FALSE + hackable = FALSE + listed_products = list( list("AUTOINJECTORS", -1, null, null), list("First-Aid Autoinjector", 8, /obj/item/reagent_container/hypospray/autoinjector/skillless, VENDOR_ITEM_REGULAR), @@ -477,16 +816,9 @@ list("Roll of Gauze", 8, /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), list("Splints", 8, /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR) ) - stack_refill = list( - /obj/item/stack/medical/ointment, - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/splint, - ) - unacidable = TRUE - unslashable = TRUE - wrenchable = FALSE - hackable = FALSE + chem_refill_volume = 500 + chem_refill_volume_max = 500 /obj/structure/machinery/cm_vending/sorted/medical/wall_med/populate_product_list(scale) return @@ -494,18 +826,19 @@ /obj/structure/machinery/cm_vending/sorted/medical/wall_med/souto name = "\improper SoutoMed" desc = "In Soutoland (Trademark pending), one is never more than 6ft away from canned Havana goodness. Drink a Souto today! For a full selection of Souto products please visit a licensed retailer or vending machine. Also doubles as basic first aid station." - icon_state = "soutomed" icon = 'icons/obj/structures/souto_land.dmi' + icon_state = "soutomed" + listed_products = list( list("FIRST AID SUPPLIES", -1, null, null), - list("First-Aid Autoinjector", 1, /obj/item/reagent_container/hypospray/autoinjector/skillless, VENDOR_ITEM_REGULAR), - list("Pain-Stop Autoinjector", 1, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, VENDOR_ITEM_REGULAR), - list("Roll Of Gauze", 2, /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), - list("Ointment", 2, /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), - list("Medical Splints", 1, /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR), + list("First-Aid Autoinjector", 2, /obj/item/reagent_container/hypospray/autoinjector/skillless, VENDOR_ITEM_REGULAR), + list("Pain-Stop Autoinjector", 2, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, VENDOR_ITEM_REGULAR), + list("Roll Of Gauze", 4, /obj/item/stack/medical/bruise_pack, VENDOR_ITEM_REGULAR), + list("Ointment", 4, /obj/item/stack/medical/ointment, VENDOR_ITEM_REGULAR), + list("Medical Splints", 4, /obj/item/stack/medical/splint, VENDOR_ITEM_REGULAR), list("UTILITY", -1, null, null), - list("HF2 Health Analyzer", 1, /obj/item/device/healthanalyzer, VENDOR_ITEM_REGULAR), + list("HF2 Health Analyzer", 2, /obj/item/device/healthanalyzer, VENDOR_ITEM_REGULAR), list("SOUTO", -1, null, null), list("Souto Classic", 1, /obj/item/reagent_container/food/drinks/cans/souto/classic, VENDOR_ITEM_REGULAR), diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm index 5ba8904069c3..276f0b760842 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm @@ -207,6 +207,7 @@ req_one_access = list(ACCESS_MARINE_ALPHA, ACCESS_MARINE_DATABASE, ACCESS_MARINE_CARGO) /obj/structure/machinery/cm_vending/sorted/uniform_supply/squad_prep/alpha/populate_product_list(scale) + ..() listed_products += list( list("HEADSET", -1, null, null), list("Marine Alpha Radio Headset", 10, /obj/item/device/radio/headset/almayer/marine/alpha, VENDOR_ITEM_REGULAR), @@ -217,6 +218,7 @@ req_one_access = list(ACCESS_MARINE_BRAVO, ACCESS_MARINE_DATABASE, ACCESS_MARINE_CARGO) /obj/structure/machinery/cm_vending/sorted/uniform_supply/squad_prep/bravo/populate_product_list(scale) + ..() listed_products += list( list("HEADSET", -1, null, null), list("Marine Bravo Radio Headset", 10, /obj/item/device/radio/headset/almayer/marine/bravo, VENDOR_ITEM_REGULAR), @@ -227,6 +229,7 @@ req_one_access = list(ACCESS_MARINE_CHARLIE, ACCESS_MARINE_DATABASE, ACCESS_MARINE_CARGO) /obj/structure/machinery/cm_vending/sorted/uniform_supply/squad_prep/charlie/populate_product_list(scale) + ..() listed_products += list( list("HEADSET", -1, null, null), list("Marine Charlie Radio Headset", 10, /obj/item/device/radio/headset/almayer/marine/charlie, VENDOR_ITEM_REGULAR), @@ -237,6 +240,7 @@ req_one_access = list(ACCESS_MARINE_DELTA, ACCESS_MARINE_DATABASE, ACCESS_MARINE_CARGO) /obj/structure/machinery/cm_vending/sorted/uniform_supply/squad_prep/delta/populate_product_list(scale) + ..() listed_products += list( list("HEADSET", -1, null, null), list("Marine Delta Radio Headset", 10, /obj/item/device/radio/headset/almayer/marine/delta, VENDOR_ITEM_REGULAR), diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 4138cb4311ac..5d38f238023f 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -15,10 +15,14 @@ var/list/datum/stack_recipe/recipes var/singular_name var/amount = 1 - var/max_amount //also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount - var/stack_id //used to determine if two stacks are of the same kind. - var/amount_sprites = FALSE //does it have sprites for extra amount, like metal, plasteel, or wood - var/display_maptext = TRUE //does it show amount on top of the icon + ///also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount + var/max_amount + ///used to determine if two stacks are of the same kind. + var/stack_id + ///does it have sprites for extra amount, like metal, plasteel, or wood + var/amount_sprites = FALSE + ///does it show amount on top of the icon + var/display_maptext = TRUE //Coords for contents display, to make it play nice with inventory borders. maptext_x = 4 maptext_y = 3 @@ -330,48 +334,48 @@ Also change the icon to reflect the amount of sheets, if possible.*/ return if(!use(desired)) return - var/obj/item/stack/newstack = new src.type(user, desired) + var/obj/item/stack/newstack = new type(user, desired) transfer_fingerprints_to(newstack) user.put_in_hands(newstack) - src.add_fingerprint(user) + add_fingerprint(user) newstack.add_fingerprint(user) - if(src && usr.interactee==src) - INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/stack, interact), usr) + if(!QDELETED(src) && user.interactee == src) + INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/stack, interact), user) return TRUE - else - return ..() + + return ..() /obj/item/stack/attack_hand(mob/user as mob) - if (user.get_inactive_hand() == src) - var/obj/item/stack/F = new src.type(user, 1) - transfer_fingerprints_to(F) - user.put_in_hands(F) - src.add_fingerprint(user) - F.add_fingerprint(user) + if(user.get_inactive_hand() == src) + var/obj/item/stack/new_stack = new type(user, 1) + transfer_fingerprints_to(new_stack) + user.put_in_hands(new_stack) + add_fingerprint(user) + new_stack.add_fingerprint(user) use(1) - if (src && usr.interactee==src) - INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/stack, interact), usr) - else - ..() - return + if(!QDELETED(src) && user.interactee == src) + INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/stack, interact), user) + return + + return ..() /obj/item/stack/attackby(obj/item/W as obj, mob/user as mob) if(istype(W, /obj/item/stack)) - var/obj/item/stack/S = W - if(S.stack_id == stack_id) //same stack type - if(S.amount >= max_amount) + var/obj/item/stack/other_stack = W + if(other_stack.stack_id == stack_id) //same stack type + if(other_stack.amount >= max_amount) to_chat(user, SPAN_WARNING("The stack is full!")) return TRUE - var/to_transfer = min(src.amount, S.max_amount-S.amount) + var/to_transfer = min(amount, other_stack.max_amount - other_stack.amount) if(to_transfer <= 0) return to_chat(user, SPAN_INFO("You transfer [to_transfer] between the stacks.")) - S.add(to_transfer) - if (S && usr.interactee==S) - INVOKE_ASYNC(S, TYPE_PROC_REF(/obj/item/stack, interact), usr) - src.use(to_transfer) - if (src && usr.interactee==src) - INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/stack, interact), usr) + other_stack.add(to_transfer) + if(other_stack && user.interactee == other_stack) + INVOKE_ASYNC(other_stack, TYPE_PROC_REF(/obj/item/stack, interact), user) + use(to_transfer) + if(!QDELETED(src) && user.interactee == src) + INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/item/stack, interact), user) user.next_move = world.time + 0.3 SECONDS return TRUE diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d03b154feb6b..1dc1b1e08282 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1364,30 +1364,29 @@ remove_splints() // target = person whose splints are being removed -// source = person removing the splints -/mob/living/carbon/human/proc/remove_splints(mob/living/carbon/human/source) - var/mob/living/carbon/human/HT = src - var/mob/living/carbon/human/HS = source - - if(!istype(HS)) - HS = src - if(!istype(HS) || !istype(HT)) +// user = person removing the splints +/mob/living/carbon/human/proc/remove_splints(mob/living/carbon/human/user) + var/mob/living/carbon/human/target = src + + if(!istype(user)) + user = src + if(!istype(user) || !istype(target)) return var/cur_hand = "l_hand" - if(!HS.hand) + if(!user.hand) cur_hand = "r_hand" - if(!HS.action_busy) + if(!user.action_busy) var/list/obj/limb/to_splint = list() var/same_arm_side = FALSE // If you are trying to splint yourself, need opposite hand to splint an arm/hand - if(HS.get_limb(cur_hand).status & LIMB_DESTROYED) - to_chat(HS, SPAN_WARNING("You cannot remove splints without a hand.")) + if(user.get_limb(cur_hand).status & LIMB_DESTROYED) + to_chat(user, SPAN_WARNING("You cannot remove splints without a hand.")) return for(var/bodypart in list("l_leg","r_leg","l_arm","r_arm","r_hand","l_hand","r_foot","l_foot","chest","head","groin")) - var/obj/limb/l = HT.get_limb(bodypart) + var/obj/limb/l = target.get_limb(bodypart) if(l && (l.status & LIMB_SPLINTED)) - if(HS == HT) + if(user == target) if((bodypart in list("l_arm", "l_hand")) && (cur_hand == "l_hand")) same_arm_side = TRUE continue @@ -1398,43 +1397,45 @@ var/msg = "" // Have to use this because there are issues with the to_chat macros and text macros and quotation marks if(to_splint.len) - if(do_after(HS, HUMAN_STRIP_DELAY * HS.get_skill_duration_multiplier(SKILL_MEDICAL), INTERRUPT_ALL, BUSY_ICON_GENERIC, HT, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) + if(do_after(user, HUMAN_STRIP_DELAY * user.get_skill_duration_multiplier(SKILL_MEDICAL), INTERRUPT_ALL, BUSY_ICON_GENERIC, target, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) var/can_reach_splints = TRUE var/amount_removed = 0 if(wear_suit && istype(wear_suit,/obj/item/clothing/suit/space)) - var/obj/item/clothing/suit/space/suit = HT.wear_suit + var/obj/item/clothing/suit/space/suit = target.wear_suit if(suit.supporting_limbs && suit.supporting_limbs.len) - msg = "[HS == HT ? "your":"\proper [HT]'s"]" - to_chat(HS, SPAN_WARNING("You cannot remove the splints, [msg] [suit] is supporting some of the breaks.")) + msg = "[user == target ? "your":"\proper [target]'s"]" + to_chat(user, SPAN_WARNING("You cannot remove the splints, [msg] [suit] is supporting some of the breaks.")) can_reach_splints = FALSE if(can_reach_splints) - var/obj/item/stack/W = new /obj/item/stack/medical/splint(HS.loc) - W.amount = 0 //we checked that we have at least one bodypart splinted, so we can create it no prob. Also we need amount to be 0 - W.add_fingerprint(HS) - for(var/obj/limb/l in to_splint) + var/obj/item/stack/medical/splint/new_splint = new(user.loc) + new_splint.amount = 0 //we checked that we have at least one bodypart splinted, so we can create it no prob. Also we need amount to be 0 + new_splint.add_fingerprint(user) + for(var/obj/limb/cur_limb in to_splint) amount_removed++ - l.status &= ~LIMB_SPLINTED + cur_limb.status &= ~LIMB_SPLINTED pain.recalculate_pain() - if(l.status & LIMB_SPLINTED_INDESTRUCTIBLE) - new /obj/item/stack/medical/splint/nano(HS.loc, 1) - l.status &= ~LIMB_SPLINTED_INDESTRUCTIBLE - else if(!W.add(1)) - W = new /obj/item/stack/medical/splint(HS.loc)//old stack is dropped, time for new one - W.amount = 0 - W.add_fingerprint(HS) - W.add(1) - msg = "[HS == HT ? "their own":"\proper [HT]'s"]" - HT.visible_message(SPAN_NOTICE("[HS] removes [msg] [amount_removed>1 ? "splints":"splint"]."), \ + if(cur_limb.status & LIMB_SPLINTED_INDESTRUCTIBLE) + new /obj/item/stack/medical/splint/nano(user.loc, 1) + cur_limb.status &= ~LIMB_SPLINTED_INDESTRUCTIBLE + else if(!new_splint.add(1)) + new_splint = new(user.loc)//old stack is dropped, time for new one + new_splint.amount = 0 + new_splint.add_fingerprint(user) + new_splint.add(1) + if(new_splint.amount == 0) + qdel(new_splint) //we only removed nano splints + msg = "[user == target ? "their own":"\proper [target]'s"]" + target.visible_message(SPAN_NOTICE("[user] removes [msg] [amount_removed>1 ? "splints":"splint"]."), \ SPAN_NOTICE("Your [amount_removed>1 ? "splints are":"splint is"] removed.")) - HT.update_med_icon() + target.update_med_icon() else - msg = "[HS == HT ? "your":"\proper [HT]'s"]" - to_chat(HS, SPAN_NOTICE("You stop trying to remove [msg] splints.")) + msg = "[user == target ? "your":"\proper [target]'s"]" + to_chat(user, SPAN_NOTICE("You stop trying to remove [msg] splints.")) else if(same_arm_side) - to_chat(HS, SPAN_WARNING("You need to use the opposite hand to remove the splints on your arm and hand!")) + to_chat(user, SPAN_WARNING("You need to use the opposite hand to remove the splints on your arm and hand!")) else - to_chat(HS, SPAN_WARNING("There are no splints to remove.")) + to_chat(user, SPAN_WARNING("There are no splints to remove.")) /mob/living/carbon/human/yautja/Initialize(mapload) . = ..(mapload, new_species = "Yautja") diff --git a/code/modules/reagents/chemical_research/Chemical-Research.dm b/code/modules/reagents/chemical_research/Chemical-Research.dm index e66cb474df50..2050e7e8e607 100644 --- a/code/modules/reagents/chemical_research/Chemical-Research.dm +++ b/code/modules/reagents/chemical_research/Chemical-Research.dm @@ -115,7 +115,7 @@ GLOBAL_DATUM_INIT(chemical_data, /datum/chemical_data, new) return FALSE //Make the chem storage scale with number of dispensers storage.recharge_rate += 5 - storage.max_energy += 50 + storage.max_energy += 100 storage.energy = storage.max_energy return storage @@ -125,7 +125,7 @@ GLOBAL_DATUM_INIT(chemical_data, /datum/chemical_data, new) return FALSE //Make the chem storage scale with number of dispensers storage.recharge_rate -= 5 - storage.max_energy -= 50 + storage.max_energy -= 100 storage.energy = storage.max_energy return TRUE diff --git a/code/modules/reagents/chemistry_machinery/chem_storage.dm b/code/modules/reagents/chemistry_machinery/chem_storage.dm index fa4f2f5c6c5a..a5196147febe 100644 --- a/code/modules/reagents/chemistry_machinery/chem_storage.dm +++ b/code/modules/reagents/chemistry_machinery/chem_storage.dm @@ -12,7 +12,7 @@ var/recharge_cooldown = 15 var/recharge_rate = 10 var/energy = 50 - var/max_energy = 50 + var/max_energy = 100 unslashable = TRUE unacidable = TRUE diff --git a/code/modules/vehicles/interior/interactable/vendors.dm b/code/modules/vehicles/interior/interactable/vendors.dm index e8b03402f5d3..8069c8ba71e4 100644 --- a/code/modules/vehicles/interior/interactable/vendors.dm +++ b/code/modules/vehicles/interior/interactable/vendors.dm @@ -17,10 +17,6 @@ /obj/item/reagent_container/hypospray/autoinjector/skillless, /obj/item/reagent_container/hypospray/autoinjector/skillless/tramadol, ) - stack_refill = list( - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/ointment, - ) //MED APC version of WY Med, provides resupply for basic stuff. Provides a decent amount of cryobags for evacuating hugged marines. /obj/structure/machinery/cm_vending/sorted/medical/vehicle @@ -57,14 +53,6 @@ /obj/item/reagent_container/hypospray/autoinjector/tricord/skillless, ) - stack_refill = list( - /obj/item/stack/medical/advanced/ointment, - /obj/item/stack/medical/advanced/bruise_pack, - /obj/item/stack/medical/ointment, - /obj/item/stack/medical/bruise_pack, - /obj/item/stack/medical/splint, - ) - /obj/structure/machinery/cm_vending/sorted/medical/vehicle/populate_product_list(scale) listed_products = list( list("FIELD SUPPLIES", -1, null, null), @@ -125,7 +113,6 @@ wrenchable = FALSE hackable = FALSE density = FALSE - var/being_restocked = FALSE vend_flags = VEND_CLUTTER_PROTECTION | VEND_LIMITED_INVENTORY | VEND_TO_HAND | VEND_LOAD_AMMO_BOXES diff --git a/tgui/packages/tgui/interfaces/VendingSorted.tsx b/tgui/packages/tgui/interfaces/VendingSorted.tsx index f72ddb2c0a31..76f32cfa6fae 100644 --- a/tgui/packages/tgui/interfaces/VendingSorted.tsx +++ b/tgui/packages/tgui/interfaces/VendingSorted.tsx @@ -1,10 +1,11 @@ import { KEY_ESCAPE } from 'common/keycodes'; import { useBackend, useLocalState } from '../backend'; -import { Button, Section, Flex, Box, Tooltip, Input, NoticeBox, Icon } from '../components'; +import { Button, Section, Flex, Box, Tooltip, Input, NoticeBox, Icon, ProgressBar } from '../components'; import { Window } from '../layouts'; import { classes } from 'common/react'; import { BoxProps } from '../components/Box'; import { Table, TableCell, TableRow } from '../components/Table'; +import { toFixed } from 'common/math'; const THEME_COMP = 0; const THEME_USCM = 1; @@ -35,8 +36,11 @@ interface VendingData { theme: string; displayed_categories: VendingCategory[]; stock_listing: Array; + stock_listing_partials?: Array; show_points?: boolean; current_m_points?: number; + reagents?: number; + reagents_max?: number; } interface VenableItem { @@ -113,20 +117,24 @@ const VendableItemRow = (props: VenableItem) => { const quantity = data.stock_listing[record.prod_index - 1]; const available = quantity > 0; + const partial_quantity = + data.stock_listing_partials?.[record.prod_index - 1] ?? 0; + const partialDesignation = partial_quantity > 0 ? '*' : ''; const isMandatory = record.prod_color === VENDOR_ITEM_MANDATORY; const isRecommended = record.prod_color === VENDOR_ITEM_RECOMMENDED; return ( <> - + - + {quantity} + {partialDesignation} @@ -142,7 +150,10 @@ const VendableItemRow = (props: VenableItem) => { - + @@ -164,7 +175,7 @@ const VendableClothingItemRow = (props: { return ( <> - + @@ -192,7 +203,7 @@ const VendableClothingItemRow = (props: { @@ -256,6 +267,7 @@ export const ViewVendingCategory = (props: VendingCategoryProps) => { return ( { const { data, act } = useBackend(); if (data === undefined) { return ( - + no data! ); @@ -302,8 +314,10 @@ export const VendingSorted = () => { const isEmpty = categories.length === 0; const show_points = data.show_points ?? false; const points = data.current_m_points ?? 0; + const reagents = data.reagents ?? 0; + const reagents_max = data.reagents_max ?? 0; return ( - + { /> + {reagents_max > 0 && ( + + + Reagents + + + + {toFixed(reagents) + ' units'} + + + + )} )} diff --git a/tgui/packages/tgui/styles/interfaces/VendingSorted.scss b/tgui/packages/tgui/styles/interfaces/VendingSorted.scss index 780b2b22711e..8135619f0fdd 100644 --- a/tgui/packages/tgui/styles/interfaces/VendingSorted.scss +++ b/tgui/packages/tgui/styles/interfaces/VendingSorted.scss @@ -135,10 +135,6 @@ $mandatory-color: rgb(128, 83, 0); width: 100%; } - .ItemTable { - width: 95vw; - } - .IconCell { width: 32px; } @@ -172,6 +168,11 @@ $mandatory-color: rgb(128, 83, 0); color: white; } + .SmallIcon { + min-width: 20px; + max-width: 20px; + } + .MandatoryItemText { color: rgb(255, 200, 90); } diff --git a/tgui/packages/tgui/styles/themes/uscm.scss b/tgui/packages/tgui/styles/themes/uscm.scss index 99fd2be0b670..edfd444ff4fe 100644 --- a/tgui/packages/tgui/styles/themes/uscm.scss +++ b/tgui/packages/tgui/styles/themes/uscm.scss @@ -26,4 +26,9 @@ background-position: right 40px top 50%; background-repeat: no-repeat; } + .Vendor { + .VendingFlexAlt { + background-color: rgba(#0c0e1e, 1); + } + } }