[MIRROR] Hands management element [MDB IGNORE] (#24257)

* Hands management element

* Update basic.dm

* Update dextrous.dm

* Fix screenshot test

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-10-11 05:02:36 +02:00
committed by GitHub
parent e2bd9d8648
commit 9eed8589db
19 changed files with 151 additions and 140 deletions

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"

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,

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)

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]

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)
#undef LIVING_UNARMED_ATTACK_BLOCKED

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)
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)
return
///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)

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))].")

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

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

View File

@@ -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)

View File

@@ -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!")

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 ..()

View File

@@ -1308,7 +1308,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))

View File

@@ -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.

View File

@@ -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]", EXAMINE_SECTION_BREAK) //SKYRAT EDIT CHANGE
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 ..()
. += 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()

View File

@@ -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()

View File

@@ -984,11 +984,46 @@
/// 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)
/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

View File

@@ -59,11 +59,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 ..()

View File

@@ -1378,6 +1378,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"