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

🆑
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).
/🆑
This commit is contained in:
Jacquerel
2023-10-10 15:27:12 -06:00
committed by GitHub
parent 7bf2f55512
commit 6d59d8eb91
19 changed files with 151 additions and 140 deletions
+4
View File
@@ -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"
+2
View File
@@ -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,
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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]
+1 -22
View File
@@ -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
+7 -9
View File
@@ -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)
+69
View File
@@ -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))].")
+1 -7
View File
@@ -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
+15
View File
@@ -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
@@ -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)
@@ -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!")
+1 -39
View File
@@ -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 ..()
+1 -1
View File
@@ -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))
@@ -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.
@@ -22,17 +22,9 @@
dropItemToGround(internal_storage)
/mob/living/simple_animal/hostile/guardian/dextrous/examine(mob/user)
if(dextrous)
. = list("<span class='info'>This is [icon2html(src)] \a <b>[src]</b>!\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."
. += "</span>"
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()
@@ -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()
+37 -2
View File
@@ -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
@@ -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 ..()
+1
View File
@@ -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"