From 6d59d8eb910c57bbc003e0b0da8dcb876ac7d3cf Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Tue, 10 Oct 2023 22:27:12 +0100 Subject: [PATCH] Hands management element (#78887) ## About The Pull Request Places "setting up being able to use hands" into an element so it can be easily applied to non-carbon mobs. ## Why It's Good For The Game This is one of the last roadblocks for basic mob implementation and people were intimidated by it but it turns out this shit is easy, 90% of this code is already just on `living` and I put a little bit more there. The element _really_ only handles stuff like dropping items on death, picking things up rather than biting them, and adding examine text. This also removes some copy/pasted code between `simple_animal` and `carbon` and unifies some behaviours which were implemented for some dextrous simple animals but not others. Changes to give Ian a single hand representing his mouth will come at a later date. ## Changelog :cl: qol: You can now see what drones and gorillas are holding by examining them. admin: It's now easier to give handless mobs hands by applying the "dextrous" element. balance: Spiders and Bears can now climb railings (you know if... they'd rather do that than destroy them). /:cl: --- code/__DEFINES/traits.dm | 4 ++ code/_globalvars/traits.dm | 2 + code/_onclick/hud/generic_dextrous.dm | 2 +- code/_onclick/hud/screentip.dm | 2 +- code/_onclick/other_mobs.dm | 23 +------ code/datums/elements/climbable.dm | 16 ++--- code/datums/elements/dextrous.dm | 69 +++++++++++++++++++ code/game/machinery/_machinery.dm | 8 +-- code/modules/mob/living/basic/basic.dm | 15 ++++ .../living/basic/space_fauna/bear/_bear.dm | 2 +- .../living/basic/space_fauna/spider/spider.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 40 +---------- code/modules/mob/living/living.dm | 2 +- .../mob/living/silicon/robot/inventory.dm | 1 + .../simple_animal/guardian/types/dextrous.dm | 14 +--- .../mob/living/simple_animal/simple_animal.dm | 42 +---------- code/modules/mob/mob.dm | 39 ++++++++++- .../security_levels/keycard_authentication.dm | 7 +- tgstation.dme | 1 + 19 files changed, 151 insertions(+), 140 deletions(-) create mode 100644 code/datums/elements/dextrous.dm diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 69a575c4adc..f4d3c5aebca 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -172,6 +172,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_DEFIB_BLACKLISTED "defib_blacklisted" #define TRAIT_BADDNA "baddna" #define TRAIT_CLUMSY "clumsy" +/// Trait that means you are capable of holding items in some form +#define TRAIT_CAN_HOLD_ITEMS "can_hold_items" +/// Trait which lets you clamber over a barrier +#define TRAIT_FENCE_CLIMBER "can_climb_fences" /// means that you can't use weapons with normal trigger guards. #define TRAIT_CHUNKYFINGERS "chunkyfingers" #define TRAIT_CHUNKYFINGERS_IGNORE_BATON "chunkyfingers_ignore_baton" diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 512b528f86f..dc64545a58e 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -30,6 +30,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS, "TRAIT_CHUNKYFINGERS_IGNORE_BATON" = TRAIT_CHUNKYFINGERS_IGNORE_BATON, "TRAIT_FIST_MINING" = TRAIT_FIST_MINING, + "TRAIT_CAN_HOLD_ITEMS" = TRAIT_CAN_HOLD_ITEMS, + "TRAIT_FENCE_CLIMBER" = TRAIT_FENCE_CLIMBER, "TRAIT_DUMB" = TRAIT_DUMB, "TRAIT_ADVANCEDTOOLUSER" = TRAIT_ADVANCEDTOOLUSER, "TRAIT_DISCOORDINATED_TOOL_USER" = TRAIT_DISCOORDINATED_TOOL_USER, diff --git a/code/_onclick/hud/generic_dextrous.dm b/code/_onclick/hud/generic_dextrous.dm index bf09fa33717..64ad896d57a 100644 --- a/code/_onclick/hud/generic_dextrous.dm +++ b/code/_onclick/hud/generic_dextrous.dm @@ -43,7 +43,7 @@ using.icon = ui_style static_inventory += using - mymob.canon_client.clear_screen() + mymob.canon_client?.clear_screen() for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory)) if(inv.slot_id) diff --git a/code/_onclick/hud/screentip.dm b/code/_onclick/hud/screentip.dm index 107f4ce1be5..94b8f591f65 100644 --- a/code/_onclick/hud/screentip.dm +++ b/code/_onclick/hud/screentip.dm @@ -14,7 +14,7 @@ /atom/movable/screen/screentip/proc/update_view(datum/source) SIGNAL_HANDLER - if(!hud || !hud.mymob.canon_client.view_size) //Might not have been initialized by now + if(!hud || !hud.mymob.canon_client?.view_size) //Might not have been initialized by now return maptext_width = view_to_pixels(hud.mymob.canon_client.view_size.getView())[1] diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 1868057d82c..ed1cdc57b76 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -287,34 +287,13 @@ /atom/proc/attack_pai_secondary(mob/user, list/modifiers) return SECONDARY_ATTACK_CALL_NORMAL -/* - Simple animals -*/ - -/mob/living/simple_animal/resolve_unarmed_attack(atom/attack_target, list/modifiers) - if(dextrous && (isitem(attack_target) || !combat_mode)) - attack_target.attack_hand(src, modifiers) - update_held_items() - else - return ..() - -/mob/living/simple_animal/resolve_right_click_attack(atom/target, list/modifiers) - if(dextrous && (isitem(target) || !combat_mode)) - . = target.attack_hand_secondary(src, modifiers) - update_held_items() - else - return ..() - /* Hostile animals */ /mob/living/simple_animal/hostile/resolve_unarmed_attack(atom/attack_target, list/modifiers) GiveTarget(attack_target) - if(dextrous && (isitem(attack_target) || !combat_mode)) - return ..() - else - INVOKE_ASYNC(src, PROC_REF(AttackingTarget), attack_target) + return ..() #undef LIVING_UNARMED_ATTACK_BLOCKED diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm index e953766571c..b26990c5911 100644 --- a/code/datums/elements/climbable.dm +++ b/code/datums/elements/climbable.dm @@ -114,15 +114,13 @@ ///Handles climbing onto the atom when you click-drag /datum/element/climbable/proc/mousedrop_receive(atom/climbed_thing, atom/movable/dropped_atom, mob/user, params) SIGNAL_HANDLER - if(user == dropped_atom && isliving(dropped_atom)) - var/mob/living/living_target = dropped_atom - if(isanimal(living_target)) - var/mob/living/simple_animal/animal = dropped_atom - if (!animal.dextrous) - return - if(living_target.mobility_flags & MOBILITY_MOVE) - INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) - return + if(user != dropped_atom || !isliving(dropped_atom)) + return + if(!HAS_TRAIT(dropped_atom, TRAIT_FENCE_CLIMBER) && !HAS_TRAIT(dropped_atom, TRAIT_CAN_HOLD_ITEMS)) // If you can hold items you can probably climb a fence + return + var/mob/living/living_target = dropped_atom + if(living_target.mobility_flags & MOBILITY_MOVE) + INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) ///Tries to climb onto the target if the forced movement of the mob allows it /datum/element/climbable/proc/try_speedrun(datum/source, mob/bumpee) diff --git a/code/datums/elements/dextrous.dm b/code/datums/elements/dextrous.dm new file mode 100644 index 00000000000..335c7c196d1 --- /dev/null +++ b/code/datums/elements/dextrous.dm @@ -0,0 +1,69 @@ +/** + * Sets up the attachee to have hands and manages things like dropping items on death and displaying them on examine + * Actual hand performance is managed by code on /living/ and not encapsulated here, we just enable it + */ +/datum/element/dextrous + +/datum/element/dextrous/Attach(datum/target, hands_count = 2, hud_type = /datum/hud/dextrous) + . = ..() + if (!isliving(target) || iscarbon(target)) + return ELEMENT_INCOMPATIBLE // Incompatible with the carbon typepath because that already has its own hand handling and doesn't need hand holding + + var/mob/living/mob_parent = target + set_available_hands(mob_parent, hands_count) + mob_parent.set_hud_used(new hud_type(target)) + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + ADD_TRAIT(target, TRAIT_CAN_HOLD_ITEMS, REF(src)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_hand_clicked)) + RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + +/datum/element/dextrous/Detach(datum/source) + . = ..() + var/mob/living/mob_parent = source + set_available_hands(mob_parent, initial(mob_parent.default_num_hands)) + var/initial_hud = initial(mob_parent.hud_type) + mob_parent.set_hud_used(new initial_hud(source)) + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + REMOVE_TRAIT(source, TRAIT_CAN_HOLD_ITEMS, REF(src)) + UnregisterSignal(source, list( + COMSIG_ATOM_EXAMINE, + COMSIG_LIVING_DEATH, + COMSIG_LIVING_UNARMED_ATTACK, + )) + +/// Set up how many hands we should have +/datum/element/dextrous/proc/set_available_hands(mob/living/hand_owner, hands_count) + hand_owner.drop_all_held_items() + var/held_items = list() + for (var/i in 1 to hands_count) + held_items += null + hand_owner.held_items = held_items + hand_owner.set_num_hands(hands_count) + hand_owner.set_usable_hands(hands_count) + +/// Drop our shit when we die +/datum/element/dextrous/proc/on_death(mob/living/died, gibbed) + SIGNAL_HANDLER + died.drop_all_held_items() + +/// Try picking up items +/datum/element/dextrous/proc/on_hand_clicked(mob/living/hand_haver, atom/target, proximity, modifiers) + SIGNAL_HANDLER + if (!isitem(target) && hand_haver.combat_mode) + return + if (LAZYACCESS(modifiers, RIGHT_CLICK)) + INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand_secondary), hand_haver, modifiers) + else + INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand), hand_haver, modifiers) + INVOKE_ASYNC(hand_haver, TYPE_PROC_REF(/mob, update_held_items)) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Tell people what we are holding +/datum/element/dextrous/proc/on_examined(mob/living/examined, mob/user, list/examine_list) + SIGNAL_HANDLER + for(var/obj/item/held_item in examined.held_items) + if(held_item.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + examine_list += span_info("[examined.p_They()] [examined.p_have()] [held_item.get_examine_string(user)] in [examined.p_their()] \ + [examined.get_held_index_name(examined.get_held_index_of_item(held_item))].") diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index d245a0a317d..444eff17006 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -591,13 +591,7 @@ if(!isliving(user)) return FALSE //no ghosts allowed, sorry - var/is_dextrous = FALSE - if(isanimal(user)) - var/mob/living/simple_animal/user_as_animal = user - if (user_as_animal.dextrous) - is_dextrous = TRUE - - if(!issilicon(user) && !is_dextrous && !user.can_hold_items()) + if(!issilicon(user) && !user.can_hold_items()) return FALSE //spiders gtfo if(issilicon(user)) // If we are a silicon, make sure the machine allows silicons to interact with it diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm index ab8daf47635..1e9a17d69c5 100644 --- a/code/modules/mob/living/basic/basic.dm +++ b/code/modules/mob/living/basic/basic.dm @@ -285,6 +285,21 @@ return last_icon_state return null +/mob/living/basic/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, ignore_animation = TRUE) + . = ..() + if (.) + update_held_items() + +/mob/living/basic/update_held_items() + if(isnull(client) || isnull(hud_used) || hud_used.hud_version == HUD_STYLE_NOHUD) + return + var/turf/our_turf = get_turf(src) + for(var/obj/item/held in held_items) + var/index = get_held_index_of_item(held) + SET_PLANE(held, ABOVE_HUD_PLANE, our_turf) + held.screen_loc = ui_hand_position(index) + client.screen |= held + /mob/living/basic/get_body_temp_heat_damage_limit() return maximum_survivable_temperature diff --git a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm index 414f28a1e9a..924cf854276 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm @@ -42,7 +42,7 @@ /mob/living/basic/bear/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + add_traits(list(TRAIT_SPACEWALK, TRAIT_FENCE_CLIMBER), INNATE_TRAIT) AddElement(/datum/element/ai_retaliate) AddComponent(/datum/component/tree_climber, climbing_distance = 15) AddElement(/datum/element/swabable, CELL_LINE_TABLE_BEAR, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider.dm b/code/modules/mob/living/basic/space_fauna/spider/spider.dm index 4bd773f6d0a..53b48129e2e 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider.dm @@ -49,7 +49,7 @@ /mob/living/basic/spider/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_WEB_SURFER, INNATE_TRAIT) + add_traits(list(TRAIT_WEB_SURFER, TRAIT_FENCE_CLIMBER), INNATE_TRAIT) AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 199590e3dc6..eb72b273453 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -10,6 +10,7 @@ COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(disarm_collision), ) AddElement(/datum/element/connect_loc, loc_connections) + ADD_TRAIT(src, TRAIT_CAN_HOLD_ITEMS, INNATE_TRAIT) // Carbons are assumed to be innately capable of having arms, we check their arms count instead /mob/living/carbon/Destroy() //This must be done first, so the mob ghosts correctly before DNA etc is nulled @@ -27,45 +28,6 @@ QDEL_NULL(dna) GLOB.carbon_list -= src -/mob/living/carbon/perform_hand_swap(held_index) - . = ..() - if(!.) - return - - if(!held_index) - held_index = (active_hand_index % held_items.len)+1 - - if(!isnum(held_index)) - CRASH("You passed [held_index] into swap_hand instead of a number. WTF man") - - var/oindex = active_hand_index - active_hand_index = held_index - if(hud_used) - var/atom/movable/screen/inventory/hand/H - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_appearance() - H = hud_used.hand_slots["[held_index]"] - if(H) - H.update_appearance() - - -/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() // Activate held item - /mob/living/carbon/attackby(obj/item/item, mob/living/user, params) if(!all_wounds || !(!user.combat_mode || user == src)) return ..() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index ba5c04ec460..5c5401b376f 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1233,7 +1233,7 @@ return /mob/living/can_hold_items(obj/item/I) - return usable_hands && ..() + return ..() && HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS) && usable_hands /mob/living/can_perform_action(atom/movable/target, action_bitflags) if(!istype(target)) diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 7df2e7d3390..be713b429a6 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -401,6 +401,7 @@ /mob/living/silicon/robot/perform_hand_swap() cycle_modules() + return TRUE /mob/living/silicon/robot/can_hold_items(obj/item/I) return (I && (I in model.modules)) //Only if it's part of our model. diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm index dc4d8a004c6..d2fbfc33c87 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -22,17 +22,9 @@ dropItemToGround(internal_storage) /mob/living/simple_animal/hostile/guardian/dextrous/examine(mob/user) - if(dextrous) - . = list("This is [icon2html(src)] \a [src]!\n[desc]") - for(var/obj/item/held_item in held_items) - if(held_item.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) - continue - . += "It has [held_item.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(held_item))]." - if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) - . += "It is holding [internal_storage.get_examine_string(user)] in its internal storage." - . += "" - else - return ..() + . = ..() + if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) + . += span_info("It is holding [internal_storage.get_examine_string(user)] in its internal storage.") /mob/living/simple_animal/hostile/guardian/dextrous/recall_effects() drop_all_held_items() diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 3b16ab685eb..4962e25afe0 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -177,6 +177,7 @@ stack_trace("Simple animal being instantiated in nullspace") update_simplemob_varspeed() if(dextrous) + AddElement(/datum/element/dextrous, hud_type = hud_type) AddComponent(/datum/component/personal_crafting) add_traits(list(TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP), ROUNDSTART_TRAIT) ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) @@ -447,9 +448,6 @@ /mob/living/simple_animal/death(gibbed) drop_loot() - if(dextrous) - drop_all_held_items() - if(del_on_death) ..() //Prevent infinite loops if the mob Destroy() is overridden in such @@ -560,44 +558,6 @@ /mob/living/simple_animal/get_idcard(hand_first) return (..() || access_card) -/mob/living/simple_animal/can_hold_items(obj/item/I) - return dextrous && ..() - -/mob/living/simple_animal/activate_hand(selhand) - if(!dextrous) - return ..() - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() - -/mob/living/simple_animal/perform_hand_swap(hand_index) - . = ..() - if(!.) - return - if(!dextrous) - return - if(!hand_index) - hand_index = (active_hand_index % held_items.len)+1 - var/oindex = active_hand_index - active_hand_index = hand_index - if(hud_used) - var/atom/movable/screen/inventory/hand/H - H = hud_used.hand_slots["[hand_index]"] - if(H) - H.update_appearance() - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_appearance() - /mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, ignore_animation = TRUE) . = ..() update_held_items() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c5a6fd74e72..cb6469d16a8 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -937,10 +937,45 @@ /// Performs the actual ritual of swapping hands, such as setting the held index variables /mob/proc/perform_hand_swap(held_index) PROTECTED_PROC(TRUE) + if (!HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS)) + return FALSE + + if(!held_index) + held_index = (active_hand_index % held_items.len) + 1 + + if(!isnum(held_index)) + CRASH("You passed [held_index] into swap_hand instead of a number. WTF man") + + var/previous_index = active_hand_index + active_hand_index = held_index + if(hud_used) + var/atom/movable/screen/inventory/hand/held_location + held_location = hud_used.hand_slots["[previous_index]"] + if(!isnull(held_location)) + held_location.update_appearance() + held_location = hud_used.hand_slots["[held_index]"] + if(!isnull(held_location)) + held_location.update_appearance() return TRUE -/mob/proc/activate_hand(selhand) - return +/mob/proc/activate_hand(selected_hand) + if (!HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS)) + return + + if(!selected_hand) + selected_hand = (active_hand_index % held_items.len)+1 + + if(istext(selected_hand)) + selected_hand = lowertext(selected_hand) + if(selected_hand == "right" || selected_hand == "r") + selected_hand = 2 + if(selected_hand == "left" || selected_hand == "l") + selected_hand = 1 + + if(selected_hand != active_hand_index) + swap_hand(selected_hand) + else + mode() /mob/proc/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //For sec bot threat assessment return 0 diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 66bf8b3bc93..6d3d9326d6d 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -55,11 +55,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/keycard_auth, 26) /obj/machinery/keycard_auth/ui_status(mob/user) if(isdrone(user)) return UI_CLOSE - if(!isanimal(user)) + if(!isanimal_or_basicmob(user)) return ..() - var/mob/living/simple_animal/A = user - if(!A.dextrous) - to_chat(user, span_warning("You are too primitive to use this device!")) + if(!HAS_TRAIT(user, TRAIT_CAN_HOLD_ITEMS)) + balloon_alert(user, "no hands!") return UI_CLOSE return ..() diff --git a/tgstation.dme b/tgstation.dme index add129c2081..4562f074b5a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1291,6 +1291,7 @@ #include "code\datums\elements\death_gases.dm" #include "code\datums\elements\delete_on_drop.dm" #include "code\datums\elements\deliver_first.dm" +#include "code\datums\elements\dextrous.dm" #include "code\datums\elements\diggable.dm" #include "code\datums\elements\digitalcamo.dm" #include "code\datums\elements\drag_pickup.dm"