Files
Bubberstation/code/datums/components/prosthetic_item.dm
MrMelbert 601f6faea5 Chainsaw nullrod fix (#93283)
## About The Pull Request

Fixes #93280

It would give you the null rod arm, then equip the null rod, then try to
give you the null rod arm again, which removed your null rod arm and
caused... problems

## Changelog

🆑 Melbert
fix: Fixed chainsaw nullrod
/🆑
2025-10-05 16:24:35 -06:00

157 lines
6.7 KiB
Plaintext

/**
* This makes an arbitrary item into a "prosthetic limb"
*/
/datum/component/item_as_prosthetic_limb
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// The item that is the prosthetic limb
VAR_PRIVATE/obj/item/item_limb
/// Prob of losing the arm on attack
var/drop_prob = 100
/datum/component/item_as_prosthetic_limb/Initialize(obj/item/prosthetic_item, drop_prob = 100)
if(!isbodypart(parent))
return COMPONENT_INCOMPATIBLE
var/obj/item/bodypart/bodyparent = parent
if(bodyparent.body_zone != BODY_ZONE_L_ARM && bodyparent.body_zone != BODY_ZONE_R_ARM)
return COMPONENT_INCOMPATIBLE
if(isnull(bodyparent.owner))
return COMPONENT_INCOMPATIBLE
bodyparent.bodypart_flags |= BODYPART_PSEUDOPART|BODYPART_IMPLANTED
src.item_limb = prosthetic_item // calls register_item() in registerWithParent()
src.drop_prob = drop_prob
/datum/component/item_as_prosthetic_limb/InheritComponent(datum/component/new_comp, i_am_original, obj/item/prosthetic_item, drop_prob = 100)
if(prosthetic_item)
unregister_item(item_limb)
register_item(prosthetic_item)
src.drop_prob = drop_prob
/datum/component/item_as_prosthetic_limb/proc/register_item(obj/item/prosthetic_item)
SEND_SIGNAL(prosthetic_item, COMSIG_ITEM_PRE_USED_AS_PROSTHETIC, parent)
item_limb = prosthetic_item
RegisterSignal(prosthetic_item, COMSIG_QDELETING, PROC_REF(qdel_limb))
RegisterSignal(prosthetic_item, COMSIG_MOVABLE_MOVED, PROC_REF(limb_moved))
// adds a bunch of flags to the item to make it act like a limb
prosthetic_item.item_flags |= (HAND_ITEM|ABSTRACT)
prosthetic_item.interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP
ADD_TRAIT(prosthetic_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
// update the name of the real limb to match the item, so you see "their chainsaw arm is wounded"
var/obj/item/bodypart/bodyparent = parent
// ensures the item is in the proper place
switch(bodyparent.body_zone)
if(BODY_ZONE_R_ARM)
bodyparent.name = "right [prosthetic_item.name]"
bodyparent.plaintext_zone = "right [prosthetic_item.name]"
bodyparent.owner.put_in_r_hand(prosthetic_item)
if(BODY_ZONE_L_ARM)
bodyparent.name = "left [prosthetic_item.name]"
bodyparent.plaintext_zone = "left [prosthetic_item.name]"
bodyparent.owner.put_in_l_hand(prosthetic_item)
SEND_SIGNAL(prosthetic_item, COMSIG_ITEM_POST_USED_AS_PROSTHETIC, parent)
/datum/component/item_as_prosthetic_limb/proc/unregister_item(obj/item/prosthetic_item)
item_limb = null
UnregisterSignal(prosthetic_item, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
// nothing to be done if it's being thrown away
if(QDELING(prosthetic_item))
return
// reset all the flags
prosthetic_item.item_flags &= ~(HAND_ITEM|ABSTRACT)
prosthetic_item.interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP
REMOVE_TRAIT(prosthetic_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
// note: default behavior is to keep the item in the contents of the arm
// so if the limb is deleted by some unknown means (supermatter?), the item will also be deleted
SEND_SIGNAL(prosthetic_item, COMSIG_ITEM_DROPPED_FROM_PROSTHETIC, parent)
/datum/component/item_as_prosthetic_limb/proc/register_limb(obj/item/bodypart/bodyparent)
RegisterSignal(bodyparent, COMSIG_BODYPART_REMOVED, PROC_REF(clear_comp))
RegisterSignals(bodyparent.owner, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK), PROC_REF(pop_limb))
/datum/component/item_as_prosthetic_limb/proc/unregister_limb(obj/item/bodypart/bodyparent)
UnregisterSignal(bodyparent, COMSIG_BODYPART_REMOVED)
if(bodyparent.owner) // may be null frim removal
UnregisterSignal(bodyparent.owner, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK))
/datum/component/item_as_prosthetic_limb/RegisterWithParent()
register_limb(parent)
register_item(item_limb)
/datum/component/item_as_prosthetic_limb/UnregisterFromParent()
unregister_limb(parent)
unregister_item(item_limb)
/// If the fake limb (the item) is deleted, the real limb goes with it.
/datum/component/item_as_prosthetic_limb/proc/qdel_limb(obj/item/source)
SIGNAL_HANDLER
if(QDELING(parent))
return
qdel(parent) // which nulls all the references and signals
/// If the item is removed from our hands somehow, the real limb has to go
/datum/component/item_as_prosthetic_limb/proc/limb_moved(obj/item/source)
SIGNAL_HANDLER
if(QDELING(parent))
return
var/obj/item/bodypart/bodyparent = parent
if(source.loc == bodyparent.owner)
return
qdel(parent) // which nulls all the references and signals
/// When the bodypart is removed, we will drop the item on the ground, and then delete the the real limb.
/datum/component/item_as_prosthetic_limb/proc/clear_comp(datum/source, mob/living/carbon/owner)
SIGNAL_HANDLER
UnregisterSignal(owner, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK))
if(item_limb.loc == owner)
item_limb.forceMove(owner.drop_location())
if(QDELING(parent))
return
qdel(parent) // which nulls all the references and signals
/// Attacking with the fake limb (the item) can cause it to fall off, which in turn will result in the real limb being deleted.
/datum/component/item_as_prosthetic_limb/proc/pop_limb(mob/living/source, ...)
SIGNAL_HANDLER
if(source.get_active_hand() != parent)
return NONE
if(!prob(drop_prob))
return NONE
var/obj/item/bodypart/bodyparent = parent
source.visible_message(
span_warning("As [source] attempts to swing with [source.p_their()] [bodyparent.name], it falls right off!"),
span_warning("As you attempt to swing with [source.p_their()] [bodyparent.name], it falls right off!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
bodyparent.dismember(silent = TRUE) // which removes the limb, which qdels us (which nulls all the references and signals)
return COMPONENT_CANCEL_ATTACK_CHAIN
/**
* Makes the passed item into a "prosthetic limb" of this mod, replacing any existing arm
*
* Returns the created pseudopart
*/
/mob/living/carbon/proc/make_item_prosthetic(obj/item/some_thing, target_zone = BODY_ZONE_R_ARM, fall_prob = 0)
if(HAS_TRAIT_FROM(some_thing, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT))
CRASH("make_item_prosthetic given an item that is already a prosthetic limb!")
var/obj/item/bodypart/existing = get_bodypart(target_zone)
existing?.drop_limb(special = TRUE)
var/obj/item/bodypart/bodypart_to_attach = newBodyPart(target_zone)
bodypart_to_attach.change_appearance(icon = 'icons/mob/augmentation/surplus_augments.dmi', id = BODYPART_ID_ROBOTIC, greyscale = FALSE, dimorphic = FALSE)
bodypart_to_attach.try_attach_limb(src)
bodypart_to_attach.AddComponent(/datum/component/item_as_prosthetic_limb, some_thing, fall_prob)
if(bodypart_to_attach.owner != src)
stack_trace("make_item_prosthetic failed to attach to owner!")
qdel(bodypart_to_attach)
return null
return bodypart_to_attach