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); + } + } }