From 7f29afc8ec8fecc649b5761ba6f290197b8790be Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 10 Mar 2023 03:39:41 +0100 Subject: [PATCH] [MIRROR] Refactors species mutanthands into human component [MDB IGNORE] (#19355) * Refactors species mutanthands into human component * wew --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com> --- .../signals/signals_mob/signals_mob_carbon.dm | 5 +- code/datums/components/mutant_hands.dm | 170 ++++++++++++++++++ .../items/devices/scanners/health_analyzer.dm | 1 - .../heretic/knowledge/side_void_blade.dm | 23 +-- code/modules/mob/inventory.dm | 1 - code/modules/mob/living/carbon/examine.dm | 7 +- .../mob/living/carbon/human/examine.dm | 7 +- .../mob/living/carbon/human/inventory.dm | 3 - .../mob/living/carbon/human/species.dm | 17 -- .../carbon/human/species_types/zombies.dm | 9 +- .../simple_animal/friendly/drone/_drone.dm | 7 +- .../simple_animal/guardian/types/dextrous.dm | 5 +- .../surgery/bodyparts/dismemberment.dm | 4 +- code/modules/unit_tests/_unit_tests.dm | 1 + .../unit_tests/mutant_hands_consistency.dm | 71 ++++++++ code/modules/zombie/items.dm | 31 +--- .../modules/mutants/code/mutant_species.dm | 20 ++- tgstation.dme | 1 + 18 files changed, 296 insertions(+), 87 deletions(-) create mode 100644 code/datums/components/mutant_hands.dm create mode 100644 code/modules/unit_tests/mutant_hands_consistency.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index 719f873beb9..b3237d22ebc 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -32,6 +32,8 @@ #define COMPONENT_NO_ATTACH (1<<0) ///from base of /obj/item/bodypart/proc/try_attach_limb(): (new_limb, special) #define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb" +///from base of /obj/item/bodypart/proc/try_attach_limb(): (new_limb, special) +#define COMSIG_CARBON_POST_ATTACH_LIMB "carbon_post_attach_limb" #define COMSIG_BODYPART_GAUZED "bodypart_gauzed" // from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze) #define COMSIG_BODYPART_GAUZE_DESTROYED "bodypart_degauzed" // from [/obj/item/bodypart/proc/seep_gauze] when it runs out of absorption @@ -47,6 +49,8 @@ /// Called from carbon losing a limb /obj/item/bodypart/proc/drop_limb(obj/item/bodypart/lost_limb, dismembered) #define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" +/// Called from carbon losing a limb /obj/item/bodypart/proc/drop_limb(obj/item/bodypart/lost_limb, dismembered) +#define COMSIG_CARBON_POST_REMOVE_LIMB "carbon_post_remove_limb" /// Called from bodypart being removed /obj/item/bodypart/proc/drop_limb(mob/living/carbon/old_owner, dismembered) #define COMSIG_BODYPART_REMOVED "bodypart_removed" @@ -136,4 +140,3 @@ ///from /atom/movable/screen/alert/give/proc/handle_transfer(): (taker, item) #define COMSIG_CARBON_ITEM_GIVEN "carbon_item_given" - diff --git a/code/datums/components/mutant_hands.dm b/code/datums/components/mutant_hands.dm new file mode 100644 index 00000000000..f7e19e59fb5 --- /dev/null +++ b/code/datums/components/mutant_hands.dm @@ -0,0 +1,170 @@ +/** + * ## Mutant hands component + * + * This component applies to humans, and forces them to hold + * a certain typepath item in every hand no matter what*. + * + * For example, zombies being forced to hold "zombie claws" - disallowing them from holding items + * but giving them powerful weapons to infect people + * + * It is suggested that the item path supplied has NODROP (and likely DROPDEL), + * but nothing's preventing you from not having that. + * + * If they lose or gain hands, new mutant hands will be created immediately. + * + * Does not override nodrop items that already exist in hand slots. + * However if those nodrop items are lost, will immediately create a new mutant hand. + */ +/datum/component/mutant_hands + // First come, first serve + dupe_mode = COMPONENT_DUPE_UNIQUE + /// The item typepath that we insert into the parent's hands + var/obj/item/mutant_hand_path = /obj/item/mutant_hand + +/datum/component/mutant_hands/Initialize(obj/item/mutant_hand_path = /obj/item/mutant_hand) + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + + src.mutant_hand_path = mutant_hand_path + +/datum/component/mutant_hands/RegisterWithParent() + // Give them a hand before registering ANYTHING just so it's clean + INVOKE_ASYNC(src, PROC_REF(apply_mutant_hands)) + + RegisterSignals(parent, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_CARBON_POST_REMOVE_LIMB), PROC_REF(try_reapply_hands)) + RegisterSignal(parent, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(mob_equipped_item)) + RegisterSignal(parent, COMSIG_MOB_UNEQUIPPED_ITEM, PROC_REF(mob_dropped_item)) + +/datum/component/mutant_hands/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_CARBON_POST_ATTACH_LIMB, + COMSIG_CARBON_POST_REMOVE_LIMB, + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOB_UNEQUIPPED_ITEM, + )) + + // Remove all their hands after unregistering everything so they don't return + INVOKE_ASYNC(src, PROC_REF(remove_mutant_hands)) + +/** + * Tries to give the parent mob mutant hands. + * + * * If a hand slot is empty, places the mutanthand type into their hand. + * * If a hand slot is filled with a nodrop item, it will do nothing. + * * If a hand slot is filled with a non-nodrop item, drops the item to the ground. + * * If a hand slot is filled with a hand already, does nothing. + */ +/datum/component/mutant_hands/proc/apply_mutant_hands() + var/mob/living/carbon/human/human_parent = parent + for(var/obj/item/hand_slot as anything in human_parent.held_items) + // This slot is already a mutant hand + if(istype(hand_slot, mutant_hand_path)) + continue + // This slot is not empty + // Yes the held item lists contains nulls to represent empty hands + // It saves us a /item cast by using as anything in the loop + if(!isnull(hand_slot)) + if(HAS_TRAIT(hand_slot, TRAIT_NODROP) || (hand_slot.item_flags & ABSTRACT)) + // There's a nodrop / abstract item in the way of putting a mutant hand in + // It can stay, for now, but if it gets dropped / unequipped we'll swoop in to replace the slot + continue + // Drop any existing non-nodrop items to the ground + human_parent.dropItemToGround(hand_slot) + + // Put in hands has a sleep somewhere in there + human_parent.put_in_hands(new mutant_hand_path(), del_on_fail = TRUE) + +/** + * Removes all mutant idems from the parent's hand slots + */ +/datum/component/mutant_hands/proc/remove_mutant_hands() + var/mob/living/carbon/human/human_parent = parent + for(var/obj/item/hand_slot in human_parent.held_items) + // Not a mutant hand, don't need to delete it + if(!istype(hand_slot, mutant_hand_path)) + continue + + // Just send it to the shadow realm, this will handle unequipping and remove it for us + qdel(hand_slot) + +/** + * Signal proc for any signals that may result in the number of hands of the parent mob changing + * + * Always try to re-insert mutanthands if we gain or lose hands + */ +/datum/component/mutant_hands/proc/try_reapply_hands(datum/source) + SIGNAL_HANDLER + + if(QDELING(src) || QDELING(parent)) + return + + INVOKE_ASYNC(src, PROC_REF(apply_mutant_hands)) + +/** + * Signal proc for [COMSIG_MOB_EQUIPPED_ITEM] + * + * This is a failsafe - the mob managed to pick up something that isn't a mutant hand + */ +/datum/component/mutant_hands/proc/mob_equipped_item(mob/living/carbon/human/source, obj/item/thing, slot) + SIGNAL_HANDLER + + if(!(slot & ITEM_SLOT_HANDS)) // Who cares + return + + if(istype(thing, mutant_hand_path)) // This is definitely meant to be here + return + + if(HAS_TRAIT(thing, TRAIT_NODROP) || (thing.item_flags & ABSTRACT)) // This is meant to be here + return + + // We equipped something to hands that wasn't a mutant hand, and wasn't abstract! + // This means they're meant to have a mutant hand. So help them out. + INVOKE_ASYNC(src, PROC_REF(apply_mutant_hands)) + +/** + * Signal proc for [COMSIG_MOB_UNEQUIPPED_ITEM] + * + * This is another failsafe - the mob dropped something, maybe from their hands, so try to re-equip + */ +/datum/component/mutant_hands/proc/mob_dropped_item(mob/living/carbon/human/source, obj/item/thing) + SIGNAL_HANDLER + + if(QDELING(src) || QDELING(parent)) + return + + if(null in source.held_items) + INVOKE_ASYNC(src, PROC_REF(apply_mutant_hands)) + +/** + * Generic mutant hand type for use with the mutant hands component + * (Technically speaking, the component doesn't require you use this type. But it's here for posterity) + * + * Implements nothing except changing its icon state between left and right depending on hand slot equipped in + */ +/obj/item/mutant_hand + name = "mutant hand" + desc = "Won't somebody give me a hand?" + icon = 'icons/effects/blood.dmi' + icon_state = "bloodhand_left" + base_icon_state = "bloodhand" + item_flags = ABSTRACT | DROPDEL | HAND_ITEM + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/mutant_hand/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + +/obj/item/mutant_hand/visual_equipped(mob/user, slot) + . = ..() + + if(!base_icon_state) + return + + // Even hand indexes are right hands, + // Odd hand indexes are left hand + // ...But also, we swap it intentionally here, + // so right icon is shown on the left (Because hands) + if(user.get_held_index_of_item(src) % 2 == 1) + icon_state = "[base_icon_state]_right" + else + icon_state = "[base_icon_state]_left" diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 28d7b1d0ecf..366314f19c2 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -310,7 +310,6 @@ || targetspecies.mutantheart != initial(targetspecies.mutantheart) \ || targetspecies.mutanteyes != initial(targetspecies.mutanteyes) \ || targetspecies.mutantears != initial(targetspecies.mutantears) \ - || targetspecies.mutanthands != initial(targetspecies.mutanthands) \ || targetspecies.mutanttongue != initial(targetspecies.mutanttongue) \ || targetspecies.mutantliver != initial(targetspecies.mutantliver) \ || targetspecies.mutantstomach != initial(targetspecies.mutantstomach) \ diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm index a317196fae6..6b380eb2f0d 100644 --- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm +++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm @@ -83,43 +83,32 @@ /// Callback for the ghoul status effect - what effects are applied to the ghoul. /datum/heretic_knowledge/limited_amount/risen_corpse/proc/apply_to_risen(mob/living/risen) LAZYADD(created_items, WEAKREF(risen)) - - for(var/obj/item/held as anything in risen.held_items) - if(istype(held)) - risen.dropItemToGround(held) - - risen.put_in_hands(new /obj/item/risen_hand(), del_on_fail = TRUE) + risen.AddComponent(/datum/component/mutant_hands, mutant_hand_path = /obj/item/mutant_hand/shattered_risen) /// Callback for the ghoul status effect - cleaning up effects after the ghoul status is removed. /datum/heretic_knowledge/limited_amount/risen_corpse/proc/remove_from_risen(mob/living/risen) LAZYREMOVE(created_items, WEAKREF(risen)) - - for(var/obj/item/risen_hand/hand in risen.held_items) - qdel(hand) + qdel(risen.GetComponent(/datum/component/mutant_hands)) #undef RISEN_MAX_HEALTH /// The "hand" "weapon" used by shattered risen -/obj/item/risen_hand +/obj/item/mutant_hand/shattered_risen name = "bone-shards" desc = "What once appeared to be a normal human fist, now holds a maulled nest of sharp bone-shards." - icon = 'icons/effects/blood.dmi' - base_icon_state = "bloodhand" color = "#001aff" - item_flags = ABSTRACT | DROPDEL | HAND_ITEM - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF hitsound = SFX_SHATTER force = 16 - sharpness = SHARP_EDGED wound_bonus = -30 bare_wound_bonus = 15 demolition_mod = 1.5 + sharpness = SHARP_EDGED -/obj/item/risen_hand/Initialize(mapload) +/obj/item/mutant_hand/shattered_risen/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) -/obj/item/risen_hand/visual_equipped(mob/user, slot) +/obj/item/mutant_hand/shattered_risen/visual_equipped(mob/user, slot) . = ..() // Even hand indexes are right hands, diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 40a3d4adfbd..da0c6d55200 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -474,7 +474,6 @@ if(hud_used) hud_used.build_hand_slots() - /mob/living/carbon/human/change_number_of_hands(amt) var/old_limbs = held_items.len if(amt < old_limbs) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index f3f2304bc3e..8f8272bed02 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -18,9 +18,10 @@ if(wear_neck && !(obscured & ITEM_SLOT_NECK)) . += "[t_He] [t_is] wearing [wear_neck.get_examine_string(user)] around [t_his] neck." - for(var/obj/item/I in held_items) - if(!(I.item_flags & ABSTRACT)) - . += "[t_He] [t_is] holding [I.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(I))]." + for(var/obj/item/held_thing in held_items) + if(held_thing.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + . += "[t_He] [t_is] holding [held_thing.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(held_thing))]." if (back) . += "[t_He] [t_has] [back.get_examine_string(user)] on [t_his] back." diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 841606339dc..7dfb82a1770 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -87,9 +87,10 @@ . += "[t_He] [t_has] [back.get_examine_string(user)] on [t_his] back." //Hands - for(var/obj/item/I in held_items) - if(!(I.item_flags & ABSTRACT) && !(I.item_flags & EXAMINE_SKIP)) - . += "[t_He] [t_is] holding [I.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(I))]." + for(var/obj/item/held_thing in held_items) + if(held_thing.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + . += "[t_He] [t_is] holding [held_thing.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(held_thing))]." //gloves if(gloves && !(obscured & ITEM_SLOT_GLOVES) && !(gloves.item_flags & EXAMINE_SKIP)) diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 9d746bc7941..5ddd5700bc0 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -206,12 +206,9 @@ . += thing?.slowdown /mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) - var/index = get_held_index_of_item(I) . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should. if(!. || !I) return - if(index && !QDELETED(src) && dna.species.mutanthands) //hand freed, fill with claws, skip if we're getting deleted. - put_in_hand(new dna.species.mutanthands(), index) if(I == wear_suit) if(s_store && invdrop) dropItemToGround(s_store, TRUE) //It makes no sense for your suit storage to stay on you if you drop your suit. diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 9a224adae80..da22966ee0a 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -192,8 +192,6 @@ GLOBAL_LIST_EMPTY(features_by_species) var/obj/item/organ/internal/stomach/mutantstomach = /obj/item/organ/internal/stomach ///Replaces default appendix with a different organ. var/obj/item/organ/internal/appendix/mutantappendix = /obj/item/organ/internal/appendix - ///Forces an item into this species' hands. Only an honorary mutantthing because this is not an organ and not loaded in the same way, you've been warned to do your research. - var/obj/item/mutanthands ///Bitflag that controls what in game ways something can select this species as a spawnable source, such as magic mirrors. See [mob defines][code/__DEFINES/mobs.dm] for possible sources. var/changesource_flags = NONE @@ -469,21 +467,6 @@ GLOBAL_LIST_EMPTY(features_by_species) if(exotic_bloodtype && C.dna.blood_type != exotic_bloodtype) C.dna.blood_type = exotic_bloodtype - if(old_species.mutanthands) - for(var/obj/item/I in C.held_items) - if(istype(I, old_species.mutanthands)) - qdel(I) - - if(mutanthands) - // Drop items in hands - // If you're lucky enough to have a TRAIT_NODROP item, then it stays. - for(var/V in C.held_items) - var/obj/item/I = V - if(istype(I)) - C.dropItemToGround(I) - else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand - INVOKE_ASYNC(C, TYPE_PROC_REF(/mob, put_in_hands), new mutanthands) - if(ishuman(C)) var/mob/living/carbon/human/human = C for(var/obj/item/organ/external/organ_path as anything in external_organs) diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index 5bd23324196..6ea3c8c2d30 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -89,7 +89,6 @@ name = "Infectious Zombie" id = SPECIES_ZOMBIE_INFECTIOUS examine_limb_id = SPECIES_ZOMBIE - mutanthands = /obj/item/zombie_hand armor = 20 // 120 damage to KO a zombie, which kills it speedmod = 1.6 mutanteyes = /obj/item/organ/internal/eyes/night_vision/zombie @@ -121,6 +120,14 @@ TRAIT_STABLELIVER, // Not necessary but for consistency with above ) +/datum/species/zombie/infectious/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.AddComponent(/datum/component/mutant_hands, mutant_hand_path = /obj/item/mutant_hand/zombie) + +/datum/species/zombie/infectious/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + qdel(C.GetComponent(/datum/component/mutant_hands)) + /datum/species/zombie/infectious/check_roundstart_eligible() return FALSE diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index 3407efca7c5..cf122e92039 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -262,9 +262,10 @@ . = list("This is [icon2html(src, user)] \a [src]!", EXAMINE_SECTION_BREAK) //SKYRAT EDIT CHANGE //Hands - for(var/obj/item/I in held_items) - if(!(I.item_flags & ABSTRACT)) - . += "It has [I.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(I))]." + for(var/obj/item/held_thing in held_items) + if(held_thing.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + . += "It has [held_thing.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(held_thing))]." //Internal storage if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) 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 5a706ecef6c..06e8796896e 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -24,8 +24,9 @@ if(dextrous) . = list("This is [icon2html(src)] \a [src]!\n[desc]", EXAMINE_SECTION_BREAK) //SKYRAT EDIT CHANGE for(var/obj/item/held_item in held_items) - if(!(held_item.item_flags & ABSTRACT)) - . += "It has [held_item.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(held_item))]." + 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." . += "" diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 55de5b55885..560bde8725f 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -149,6 +149,7 @@ return forceMove(drop_loc) + SEND_SIGNAL(phantom_owner, COMSIG_CARBON_POST_REMOVE_LIMB, src, dismembered) /** * get_mangled_state() is relevant for flesh and bone bodyparts, and returns whether this bodypart has mangled skin, mangled bone, or both (or neither i guess) @@ -321,8 +322,6 @@ if(held_index > new_limb_owner.hand_bodyparts.len) new_limb_owner.hand_bodyparts.len = held_index new_limb_owner.hand_bodyparts[held_index] = src - if(new_limb_owner.dna.species.mutanthands && !is_pseudopart) - new_limb_owner.put_in_hand(new new_limb_owner.dna.species.mutanthands(), held_index) if(new_limb_owner.hud_used) var/atom/movable/screen/inventory/hand/hand = new_limb_owner.hud_used.hand_slots["[held_index]"] if(hand) @@ -368,6 +367,7 @@ new_limb_owner.updatehealth() new_limb_owner.update_body() new_limb_owner.update_damage_overlays() + SEND_SIGNAL(new_limb_owner, COMSIG_CARBON_POST_ATTACH_LIMB, src, special) return TRUE /obj/item/bodypart/head/try_attach_limb(mob/living/carbon/new_head_owner, special = FALSE, abort = FALSE) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 420091848b6..935b16f9c04 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -154,6 +154,7 @@ #include "modular_map_loader.dm" #include "monkey_business.dm" #include "mouse_bite_cable.dm" +#include "mutant_hands_consistency.dm" #include "novaflower_burn.dm" #include "ntnetwork_tests.dm" #include "nuke_cinematic.dm" diff --git a/code/modules/unit_tests/mutant_hands_consistency.dm b/code/modules/unit_tests/mutant_hands_consistency.dm new file mode 100644 index 00000000000..5543a884fec --- /dev/null +++ b/code/modules/unit_tests/mutant_hands_consistency.dm @@ -0,0 +1,71 @@ +/** + * Test: Mutant hands component + * + * Adding mutant hand component gives two mutant hands in each hand slot + * Losing a limb removes the associated hand, and re-gaining the limb re-gives the associated hand + */ +/datum/unit_test/mutant_hands + +/datum/unit_test/mutant_hands/Run() + var/mob/living/carbon/human/incredible_hulk = allocate(/mob/living/carbon/human/consistent) + var/obj/item/item_to_hold = allocate(/obj/item/storage/toolbox) + incredible_hulk.put_in_hands(item_to_hold) + incredible_hulk.AddComponent(/datum/component/mutant_hands) + + for(var/obj/item/hand as anything in incredible_hulk.held_items) + if(!istype(hand, /obj/item/mutant_hand)) + TEST_FAIL("Dummy didn't have a mutant hand on gaining mutant hands comp! Had: [hand || "nothing"].") + + var/obj/item/bodypart/left_arm = incredible_hulk.get_bodypart(BODY_ZONE_L_ARM) + left_arm.drop_limb() + + TEST_ASSERT(left_arm.try_attach_limb(incredible_hulk), "Mutant hands test failed to re-attach the limb after losing it.") + + for(var/obj/item/hand as anything in incredible_hulk.held_items) + if(!istype(hand, /obj/item/mutant_hand)) + TEST_FAIL("Dummy didn't have a mutant hand after re-gaining a limb! Had: [hand || "nothing"].") + +/** + * Test: Mutant hands component with a nodrop item in place + * + * Adding mutant hand component does not force no-drop items out of hands + * If the no-drop item disappears / is deleted, a new hand should re-appear immediately + */ +/datum/unit_test/mutant_hands_with_nodrop + +/datum/unit_test/mutant_hands_with_nodrop/Run() + var/mob/living/carbon/human/incredible_hulk = allocate(/mob/living/carbon/human/consistent) + var/obj/item/item_to_hold = allocate(/obj/item/storage/toolbox) + ADD_TRAIT(item_to_hold, TRAIT_NODROP, TRAIT_SOURCE_UNIT_TESTS) + incredible_hulk.put_in_hand(item_to_hold, 1) + incredible_hulk.AddComponent(/datum/component/mutant_hands) + + if(!istype(incredible_hulk.held_items[1], /obj/item/storage/toolbox)) + TEST_FAIL("Dummy's left hand was not a toolbox, though it was supposed to be. Was: [incredible_hulk.held_items[1] || "nothing"].") + + if(!istype(incredible_hulk.held_items[2], /obj/item/mutant_hand)) + TEST_FAIL("Dummy 's right hand was not a mutant hand! Was: [incredible_hulk.held_items[2] || "nothing"].") + + QDEL_NULL(item_to_hold) + + if(!istype(incredible_hulk.held_items[1], /obj/item/mutant_hand)) + TEST_FAIL("Dummy's left hand was not a mutant hand after losing the nodrop item. Was: [incredible_hulk.held_items[1] || "nothing"].") + +/** + * Test: Mutant hands fireman carrying + * + * Mutant hands currently do not support fireman carrying despite being theoretically allowed, + * tests that this continues to be the case. Can be updated if this assertion is changed. + */ +/datum/unit_test/mutant_hands_carry + +/datum/unit_test/mutant_hands_carry/Run() + var/mob/living/carbon/human/incredible_hulk = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/carried = allocate(/mob/living/carbon/human/consistent) + incredible_hulk.AddComponent(/datum/component/mutant_hands) + + carried.set_resting(TRUE, instant = TRUE) + + // Try a fireman carry. It should fail, we have no open hands + incredible_hulk.buckle_mob(carried, force = TRUE, check_loc = TRUE, buckle_mob_flags = CARRIER_NEEDS_ARM) + TEST_ASSERT(!length(incredible_hulk.buckled_mobs), "Someone with mutant hands was able to fireman carry, despite having no hands to do so.") diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm index 5690b72caf6..47d610dbdf6 100644 --- a/code/modules/zombie/items.dm +++ b/code/modules/zombie/items.dm @@ -1,36 +1,17 @@ -/obj/item/zombie_hand +/obj/item/mutant_hand/zombie name = "zombie claw" desc = "A zombie's claw is its primary tool, capable of infecting \ humans, butchering all other living things to \ sustain the zombie, smashing open airlock doors and opening \ child-safe caps on bottles." - item_flags = ABSTRACT | DROPDEL | HAND_ITEM - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - icon = 'icons/effects/blood.dmi' - icon_state = "bloodhand_left" - var/icon_left = "bloodhand_left" - var/icon_right = "bloodhand_right" + hitsound = 'sound/hallucinations/growl1.ogg' force = 21 // Just enough to break airlocks with melee attacks - sharpness = SHARP_EDGED wound_bonus = -30 bare_wound_bonus = 15 - damtype = BRUTE + sharpness = SHARP_EDGED -/obj/item/zombie_hand/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) - -/obj/item/zombie_hand/visual_equipped(mob/user, slot) - . = ..() - //these are intentionally inverted - var/i = user.get_held_index_of_item(src) - if(!(i % 2)) - icon_state = icon_left - else - icon_state = icon_right - -/obj/item/zombie_hand/afterattack(atom/target, mob/user, proximity_flag) +/obj/item/mutant_hand/zombie/afterattack(atom/target, mob/user, proximity_flag) . = ..() if(!proximity_flag) return @@ -59,14 +40,14 @@ infection = new() infection.Insert(target) -/obj/item/zombie_hand/suicide_act(mob/living/user) +/obj/item/mutant_hand/zombie/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is ripping [user.p_their()] brains out! It looks like [user.p_theyre()] trying to commit suicide!")) var/obj/item/bodypart/head = user.get_bodypart(BODY_ZONE_HEAD) if(head) head.dismember() return BRUTELOSS -/obj/item/zombie_hand/proc/check_feast(mob/living/target, mob/living/user) +/obj/item/mutant_hand/zombie/proc/check_feast(mob/living/target, mob/living/user) if(target.stat == DEAD) var/hp_gained = target.maxHealth target.investigate_log("has been devoured by a zombie.", INVESTIGATE_DEATHS) diff --git a/modular_skyrat/modules/mutants/code/mutant_species.dm b/modular_skyrat/modules/mutants/code/mutant_species.dm index 95ae0807fc4..a622c5477a3 100644 --- a/modular_skyrat/modules/mutants/code/mutant_species.dm +++ b/modular_skyrat/modules/mutants/code/mutant_species.dm @@ -64,20 +64,24 @@ /datum/species/mutant/infectious name = "Mutated Abomination" id = SPECIES_MUTANT_INFECTIOUS - mutanthands = /obj/item/mutant_hand speedmod = 1 armor = 10 mutanteyes = /obj/item/organ/internal/eyes/night_vision/zombie changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN + var/hands_to_give = /obj/item/hnz_mutant_hand /// The rate the mutants regenerate at var/heal_rate = 1 /// The cooldown before the mutant can start regenerating COOLDOWN_DECLARE(regen_cooldown) +/datum/species/mutant/infectious/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.AddComponent(/datum/component/mutant_hands, mutant_hand_path = hands_to_give) + /datum/species/mutant/infectious/fast name = "Fast Mutated Abomination" id = SPECIES_MUTANT_FAST - mutanthands = /obj/item/mutant_hand/fast + hands_to_give = /obj/item/hnz_mutant_hand/fast armor = 0 /// The rate the mutants regenerate at heal_rate = 0.5 @@ -131,7 +135,7 @@ else . = ..() -/obj/item/mutant_hand +/obj/item/hnz_mutant_hand name = "mutant claw" desc = "A mutant's claw is its primary tool, capable of infecting \ humans, butchering all other living things to \ @@ -152,17 +156,17 @@ var/icon_left = "bloodhand_left" var/icon_right = "bloodhand_right" -/obj/item/mutant_hand/fast +/obj/item/hnz_mutant_hand/fast name = "weak mutant claw" force = 21 sharpness = NONE wound_bonus = -40 -/obj/item/mutant_hand/Initialize(mapload) +/obj/item/hnz_mutant_hand/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) -/obj/item/mutant_hand/equipped(mob/user, slot) +/obj/item/hnz_mutant_hand/equipped(mob/user, slot) . = ..() //these are intentionally inverted var/i = user.get_held_index_of_item(src) @@ -171,7 +175,7 @@ else icon_state = icon_right -/obj/item/mutant_hand/afterattack(atom/target, mob/user, proximity_flag) +/obj/item/hnz_mutant_hand/afterattack(atom/target, mob/user, proximity_flag) . = ..() if(!proximity_flag) return @@ -217,7 +221,7 @@ if(infection) qdel(infection) -/obj/item/mutant_hand/proc/check_feast(mob/living/target, mob/living/user) +/obj/item/hnz_mutant_hand/proc/check_feast(mob/living/target, mob/living/user) if(target.stat == DEAD) var/hp_gained = target.maxHealth target.investigate_log("has been feasted upon by the mutant [user].", INVESTIGATE_DEATHS) diff --git a/tgstation.dme b/tgstation.dme index 3411ad185c2..4119ad0e0bf 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -987,6 +987,7 @@ #include "code\datums\components\mirv.dm" #include "code\datums\components\mob_harvest.dm" #include "code\datums\components\multiple_lives.dm" +#include "code\datums\components\mutant_hands.dm" #include "code\datums\components\ntnet_interface.dm" #include "code\datums\components\nuclear_bomb_operator.dm" #include "code\datums\components\omen.dm"