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"