From 9ceeb187b746d88719ea5c67d90db87dbd55265c Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 24 Feb 2026 19:38:21 -0600 Subject: [PATCH] Spraying items with lube can make them slip out of people's hands (#95224) ## About The Pull Request If you coat an item with lube, it becomes slippery. Not "clown pda" slippery but "harder to handle" slippery. Trying to pick up or use a slippery item has a chance of it falling out of your hands (Though there is another chance that you catch it, if you have an empty hand) "Use" includes arming grenades or firing guns. So if you try to arm a lubed grenade it might slip right to your feet! ## Why It's Good For The Game The clown can coat security's batons in lube. That's basically all I need to say ## Changelog :cl: Melbert add: Spraying items with lube makes them slippery, causing them to fall out of people's hands who try to pick them up or use them. /:cl: --- code/__DEFINES/dcs/signals/signals_medical.dm | 3 + code/datums/components/slippery_item.dm | 122 ++++++++++++++++++ .../chemistry/reagents/other_reagents.dm | 18 ++- code/modules/surgery/operations/_operation.dm | 6 + tgstation.dme | 1 + 5 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 code/datums/components/slippery_item.dm diff --git a/code/__DEFINES/dcs/signals/signals_medical.dm b/code/__DEFINES/dcs/signals/signals_medical.dm index 4f79b79ddd7..7429b6347f3 100644 --- a/code/__DEFINES/dcs/signals/signals_medical.dm +++ b/code/__DEFINES/dcs/signals/signals_medical.dm @@ -7,6 +7,9 @@ /// From /datum/surgery_operation/failure(): (datum/surgery_operation/operation, atom/movable/operating_on, tool) #define COMSIG_ATOM_SURGERY_FAILED "atom_surgery_step_failed" +/// From /datum/surgery_operation/try_perform(), sent to the tool: (datum/surgery_operation/operation, atom/movable/operating_on, mob/living/surgeon) +#define COMSIG_ITEM_USED_IN_SURGERY "item_used_in_surgery" + /// From /obj/item/shockpaddles/do_help, after the defib do_after is complete, but before any effects are applied: (mob/living/defibber, obj/item/shockpaddles/source) #define COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP "carbon_being_defibbed" /// Return to stop default defib handling diff --git a/code/datums/components/slippery_item.dm b/code/datums/components/slippery_item.dm new file mode 100644 index 00000000000..dbfbe3ed7da --- /dev/null +++ b/code/datums/components/slippery_item.dm @@ -0,0 +1,122 @@ +/// Item is slippery - picking it up or using it may cause it to immediately fall out of the user's hands +/datum/component/slippery_item + /// Chance the item will fall on pick up or use + var/fall_chance = 50 + /// Chance the mob will catch the item if it falls (if they have an empty hand) + var/fall_catch_chance = 0 + /// Message appended to examine when examining the item + var/examine_msg + /// Optional wash flags that removes the effect if washed + var/wash_flags = NONE + /// World.time of the last fall to avoid same tick falls + VAR_PRIVATE/last_fall + +/datum/component/slippery_item/Initialize(fall_chance = 50, fall_catch_chance = 0, examine_msg, duration = INFINITY, wash_flags = NONE) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + src.fall_chance = fall_chance + src.fall_catch_chance = fall_catch_chance + src.examine_msg = examine_msg || "It looks very slippery, and may fall out of your hands when you try to use it." + src.wash_flags = wash_flags + if(duration != INFINITY) + QDEL_IN(src, duration) + +/datum/component/slippery_item/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_cleaned)) + // bunch of generic "item used" triggers, feel free to expand + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(on_preattack)) + RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_afterattack)) + RegisterSignal(parent, COMSIG_GUN_TRY_FIRE, PROC_REF(on_tryfire)) + RegisterSignal(parent, COMSIG_GRENADE_ARMED, PROC_REF(on_grenade_arm)) + RegisterSignal(parent, COMSIG_ITEM_USED_IN_SURGERY, PROC_REF(on_surgery_started)) + +/datum/component/slippery_item/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_ATOM_EXAMINE, + COMSIG_COMPONENT_CLEAN_ACT, + COMSIG_ITEM_EQUIPPED, + COMSIG_ITEM_PRE_ATTACK, + COMSIG_ITEM_AFTERATTACK, + COMSIG_GUN_TRY_FIRE, + COMSIG_GRENADE_ARMED, + COMSIG_ITEM_USED_IN_SURGERY, + )) + +/datum/component/slippery_item/proc/on_examine(obj/item/source, mob/living/user, list/examine_list) + SIGNAL_HANDLER + + examine_list += examine_msg + +/datum/component/slippery_item/proc/on_cleaned(obj/item/source, clean_flags) + SIGNAL_HANDLER + + if(wash_flags && (wash_flags & clean_flags)) + qdel(src) + return COMPONENT_CLEANED + +/datum/component/slippery_item/proc/on_equip(obj/item/source, mob/living/user, slot) + SIGNAL_HANDLER + + if(slot & ITEM_SLOT_HANDS) + try_fall(source, user) + +/datum/component/slippery_item/proc/on_preattack(obj/item/source, atom/target, mob/living/user, ...) + SIGNAL_HANDLER + + if(try_fall(source, user)) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/component/slippery_item/proc/on_afterattack(obj/item/source, atom/target, mob/living/user, ...) + SIGNAL_HANDLER + + try_fall(source, user) + +/datum/component/slippery_item/proc/on_tryfire(obj/item/source, mob/living/user, ...) + SIGNAL_HANDLER + + if(try_fall(source, user)) + return COMPONENT_CANCEL_GUN_FIRE + +/datum/component/slippery_item/proc/on_grenade_arm(obj/item/source, ...) + SIGNAL_HANDLER + + if(isliving(source.loc)) + try_fall(source, source.loc) + +/datum/component/slippery_item/proc/on_surgery_started(obj/item/source, datum/surgery_operation/surgery, atom/movable/operating_on, mob/living/surgeon) + SIGNAL_HANDLER + + if(try_fall(source, surgeon)) + return ITEM_INTERACT_BLOCKING + +/// Check for falling and handle it if it happens. +/// Returns TRUE if the item fell, FALSE otherwise +/datum/component/slippery_item/proc/try_fall(obj/item/source, mob/living/user) + set waitfor = FALSE + // prevents fall -> catch -> fall -> catch. even though that'd be funny, you wouldn't even be able to see it happening. + if(last_fall == world.time) + return FALSE + if(!prob(fall_chance)) + return FALSE + if(source.loc != user || !user.dropItemToGround(source)) + return FALSE + if(QDELETED(source)) + return FALSE // dropdel + + playsound(source, 'sound/misc/slip.ogg', 20, TRUE, SILENCED_SOUND_EXTRARANGE) + source.SpinAnimation(4, 1) + + last_fall = world.time + if(prob(fall_catch_chance) && !HAS_TRAIT(user, TRAIT_CLUMSY)) + for(var/empty_hand in user.get_empty_held_indexes()) + if(empty_hand == user.active_hand_index) + continue + if(user.putItemFromInventoryInHandIfPossible(source, empty_hand)) + to_chat(user, span_notice("[source] slips out of your hands - but you manage to catch it, just in time.")) + return TRUE + + to_chat(user, span_warning("[source] slips out of your hands!")) + return TRUE diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 842a77a0bfe..533c1ed83cc 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -524,10 +524,20 @@ /datum/reagent/lube/expose_turf(turf/open/exposed_turf, reac_volume) . = ..() - if(!istype(exposed_turf)) - return - if(reac_volume >= 1) - exposed_turf.MakeSlippery(lube_kind, 15 SECONDS, min(reac_volume * 2 SECONDS, 120)) + if(isopenturf(exposed_turf) && reac_volume >= 1) + exposed_turf.MakeSlippery(lube_kind, 15 SECONDS, min(reac_volume * 2 SECONDS, 12 SECONDS)) + +/datum/reagent/lube/expose_obj(obj/exposed_obj, reac_volume, methods, show_message) + . = ..() + if(isitem(exposed_obj) && reac_volume >= 1) + exposed_obj.AddComponent( \ + /datum/component/slippery_item, \ + fall_chance = (lube_kind == TURF_WET_SUPERLUBE ? 80 : 50), \ + fall_catch_chance = (lube_kind == TURF_WET_SUPERLUBE ? 10 : 30), \ + examine_msg = span_info("It's coated in a slippery substance!"), \ + wash_flags = CLEAN_TYPE_HARD_DECAL, \ + duration = min(reac_volume * 3 SECONDS, 30 SECONDS), \ + ) /datum/reagent/lube/used_on_fish(obj/item/fish/fish) ADD_TRAIT(fish, TRAIT_FISH_FED_LUBE, type) //required for the lubefish mutation diff --git a/code/modules/surgery/operations/_operation.dm b/code/modules/surgery/operations/_operation.dm index aeb75785b71..ee3b036d981 100644 --- a/code/modules/surgery/operations/_operation.dm +++ b/code/modules/surgery/operations/_operation.dm @@ -837,6 +837,12 @@ GLOBAL_DATUM_INIT(operations, /datum/operation_holder, new) if(!check_availability(patient, operating_on, surgeon, tool, operation_args[OPERATION_TARGET_ZONE])) return ITEM_INTERACT_BLOCKING + if(isitem(tool)) + var/obj/item/realtool = tool + var/tool_return = SEND_SIGNAL(realtool, COMSIG_ITEM_USED_IN_SURGERY, src, operating_on, surgeon) + if(tool_return & ITEM_INTERACT_ANY_BLOCKER) + return tool_return + if(!start_operation(operating_on, surgeon, tool, operation_args)) return ITEM_INTERACT_BLOCKING diff --git a/tgstation.dme b/tgstation.dme index 5c48665d7ee..4368fc80796 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1308,6 +1308,7 @@ #include "code\datums\components\slide_under_doors.dm" #include "code\datums\components\slime_friends.dm" #include "code\datums\components\slippery.dm" +#include "code\datums\components\slippery_item.dm" #include "code\datums\components\smooth_tunes.dm" #include "code\datums\components\soapbox.dm" #include "code\datums\components\soul_stealer.dm"