Files
Bubberstation/code/datums/components/mutant_hands.dm
SmArtKar 7ddc30783a Adds better attack animations and alternate attack modes (#88418)
## About The Pull Request

This is the first PR in a series attempting to modernize our damage and
armor, both from a code and a gameplay perspective. This part implements
unique attack animations, adds alternate attack modes for items and
fixes some minor oversights.

Items now have unique attack animation based on their sharpness - sharp
items are now swung in an arc, while pointy items are thrust forward.
This change is ***purely visual***, this is not swing combat. (However,
this does assign icon rotation data to many items, which should help
swing combat later down the line).

Certain items like knives and swords now have secondary attacks - right
clicks will perform stabbing attacks instead of slashing for a chance to
leave piercing wounds, albeit with slightly lower damage - trying to
stick a katana through someone won't get you very far!

https://github.com/user-attachments/assets/1f92bbcd-9aa1-482f-bc26-5e84fe2a07e1

Turns out that spears acted as oversized knives this entire time, being
SHARP_EDGED instead of SHARP_POINTY - in order for their animations to
make sense, they're now once again pointy (according to comment,
originally they were made sharp because piercing wounds weren't very
threatening, which is no longer the case)

Another major change is that structure damage is now influenced by armor
penetration - I am not sure if this is intentional or not, but attacking
item's AP never applied to non-mob damage.

Additionally, also fixes an issue where attack verbs for you and
everyone else may differ.
2024-12-17 12:35:52 -06:00

170 lines
6.0 KiB
Plaintext

/**
* ## 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"
icon_angle = 90
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
// We swap it intentionally here,
// so right icon is shown on the left (Because hands)
if(IS_LEFT_INDEX(user.get_held_index_of_item(src)))
icon_state = "[base_icon_state]_right"
else
icon_state = "[base_icon_state]_left"