Files
Bubberstation/code/datums/components/bayonet_attachable.dm
Ghom 339616ae78 You can now interact with held mobs beside wearing them (feat: "minor" melee attack chain cleanup) (#90080)
## About The Pull Request
People can now pet held mothroaches and pugs if they want to, or use
items on them, hopefully without causing many issues. After all, it only
took about a couple dozen lines of code to make...

...Oh, did the 527 files changed or the 850~ lines added/removed perhaps
catch your eye? Made you wonder if I accidentally pushed the wrong
branch? or skewed something up big time? Well, nuh uh. I just happen to
be fed up with the melee attack chain still using stringized params
instead of an array/list. It was frankly revolting to see how I'd have
had to otherwise call `list2params` for what I'm trying to accomplish
here, and make this PR another tessera to the immense stupidity of our
attack chain procs calling `params2list` over and over and over instead
of just using that one call instance from `ClickOn` as an argument. It's
2025, honey, wake up!

I also tried to replace some of those single letter vars/args but there
are just way too many of them.

## Why It's Good For The Game
Improving old code. And I want to be able to pet mobroaches while
holding them too.

## Changelog

🆑
qol: You can now interact with held mobs in more ways beside wearing
them.
/🆑
2025-04-23 20:18:26 +00:00

213 lines
6.8 KiB
Plaintext

/**
* Component which allows you to attach a bayonet to an item,
* be it a piece of clothing or a tool.
*/
/datum/component/bayonet_attachable
/// Whenever we can remove the bayonet with a screwdriver
var/removable = TRUE
/// If passed, we wil simply update our item's icon_state when a bayonet is attached.
/// Formatted as parent_base_state-[bayonet_icon_state-state]
var/bayonet_icon_state
/// If passed, we will use a specific overlay instead of using the knife itself
/// The state to take from the bayonet overlay icon if supplied.
var/bayonet_overlay
/// This is the icon file it grabs the overlay from.
var/bayonet_overlay_icon
/// Offsets for the bayonet overlay
var/offset_x = 0
var/offset_y = 0
/// If this component allows sawing off the parent gun/should be deleted when the parent gun is sawn off
var/allow_sawnoff = FALSE
// Internal vars
/// Currently attached bayonet
var/obj/item/bayonet
/// Static typecache of all knives that can become bayonets
var/static/list/valid_bayonets = typecacheof(list(/obj/item/knife/combat))
/datum/component/bayonet_attachable/Initialize(
obj/item/starting_bayonet,
offset_x = 0,
offset_y = 0,
removable = TRUE,
bayonet_icon_state = null,
bayonet_overlay = "bayonet",
bayonet_overlay_icon = 'icons/obj/weapons/guns/bayonets.dmi',
allow_sawnoff = FALSE
)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
src.removable = removable
src.bayonet_icon_state = bayonet_icon_state
src.bayonet_overlay = bayonet_overlay
src.bayonet_overlay_icon = bayonet_overlay_icon
src.offset_x = offset_x
src.offset_y = offset_y
src.allow_sawnoff = allow_sawnoff
if (istype(starting_bayonet))
add_bayonet(starting_bayonet)
/datum/component/bayonet_attachable/Destroy(force)
if(bayonet)
remove_bayonet()
return ..()
/datum/component/bayonet_attachable/RegisterWithParent()
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(on_parent_deconstructed))
RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(on_item_exit))
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_parent_deleted))
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(on_pre_attack))
RegisterSignal(parent, COMSIG_GUN_BEING_SAWNOFF, PROC_REF(on_being_sawnoff))
RegisterSignal(parent, COMSIG_GUN_SAWN_OFF, PROC_REF(on_sawn_off))
/datum/component/bayonet_attachable/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_OBJ_DECONSTRUCT,
COMSIG_ATOM_EXITED,
COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
COMSIG_ATOM_UPDATE_ICON_STATE,
COMSIG_ATOM_UPDATE_OVERLAYS,
COMSIG_ATOM_ATTACKBY,
COMSIG_ATOM_EXAMINE,
COMSIG_QDELETING,
COMSIG_ITEM_PRE_ATTACK,
COMSIG_GUN_BEING_SAWNOFF,
COMSIG_GUN_SAWN_OFF,
))
/datum/component/bayonet_attachable/proc/on_examine(obj/item/source, mob/examiner, list/examine_list)
SIGNAL_HANDLER
if(isnull(bayonet))
examine_list += "It has a <b>bayonet</b> lug on it."
return
examine_list += "It has \a [bayonet] [removable ? "" : "permanently "]affixed to it."
if(removable)
examine_list += span_info("[bayonet] looks like it can be <b>unscrewed</b> from [bayonet].")
/datum/component/bayonet_attachable/proc/on_pre_attack(obj/item/source, atom/target, mob/living/user, list/modifiers)
SIGNAL_HANDLER
if (isnull(bayonet) || !user.combat_mode)
return NONE
INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/bayonet_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, list/modifiers)
SIGNAL_HANDLER
if(!is_type_in_typecache(attacking_item, valid_bayonets))
return
if(bayonet)
source.balloon_alert(attacker, "already has \a [bayonet]!")
return
if(!attacker.transferItemToLoc(attacking_item, source))
return
add_bayonet(attacking_item, attacker)
source.balloon_alert(attacker, "attached")
return COMPONENT_NO_AFTERATTACK
/datum/component/bayonet_attachable/proc/add_bayonet(obj/item/new_bayonet, mob/attacher)
if(bayonet)
CRASH("[type] tried to add a new bayonet when it already had one.")
bayonet = new_bayonet
if(bayonet.loc != parent)
bayonet.forceMove(parent)
var/obj/item/item_parent = parent
item_parent.update_appearance()
/datum/component/bayonet_attachable/proc/remove_bayonet()
bayonet = null
var/obj/item/item_parent = parent
item_parent.update_appearance()
/datum/component/bayonet_attachable/proc/on_item_exit(obj/item/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(gone == bayonet)
remove_bayonet()
/datum/component/bayonet_attachable/proc/on_parent_deconstructed(obj/item/source, disassembled)
SIGNAL_HANDLER
if(QDELETED(bayonet) || !removable)
remove_bayonet()
return
bayonet.forceMove(source.drop_location())
/datum/component/bayonet_attachable/proc/on_screwdriver(obj/item/source, mob/user, obj/item/tool)
SIGNAL_HANDLER
if(!bayonet || !removable)
return
INVOKE_ASYNC(src, PROC_REF(unscrew_bayonet), source, user, tool)
return ITEM_INTERACT_BLOCKING
/datum/component/bayonet_attachable/proc/unscrew_bayonet(obj/item/source, mob/user, obj/item/tool)
tool?.play_tool_sound(source)
source.balloon_alert(user, "unscrewed [bayonet]")
var/obj/item/to_remove = bayonet
to_remove.forceMove(source.drop_location())
if(source.Adjacent(user) && !issilicon(user))
user.put_in_hands(to_remove)
/datum/component/bayonet_attachable/proc/on_update_overlays(obj/item/source, list/overlays)
SIGNAL_HANDLER
if(!bayonet_overlay || !bayonet_overlay_icon)
return
if(!bayonet)
return
var/mutable_appearance/bayonet_appearance = mutable_appearance(bayonet_overlay_icon, bayonet_overlay)
bayonet_appearance.pixel_w = offset_x
bayonet_appearance.pixel_z = offset_y
overlays += bayonet_appearance
/datum/component/bayonet_attachable/proc/on_update_icon_state(obj/item/source)
SIGNAL_HANDLER
if(!bayonet_icon_state)
return
var/base_state = source.base_icon_state || initial(source.icon_state)
if(bayonet)
source.icon_state = "[base_state]-[bayonet_icon_state]"
else if(source.icon_state != base_state)
source.icon_state = base_state
/datum/component/bayonet_attachable/proc/on_parent_deleted(obj/item/source)
SIGNAL_HANDLER
QDEL_NULL(bayonet)
/datum/component/bayonet_attachable/proc/on_being_sawnoff(obj/item/source, mob/user)
SIGNAL_HANDLER
if (!bayonet || allow_sawnoff)
return
source.balloon_alert(user, "bayonet must be removed!")
return COMPONENT_CANCEL_SAWING_OFF
/datum/component/bayonet_attachable/proc/on_sawn_off(obj/item/source, mob/user)
SIGNAL_HANDLER
if (!allow_sawnoff)
qdel(src)