Files
Bubberstation/code/datums/components/bayonet_attachable.dm
Ghom 11d82b7995 You can now interact with held mobs beside wearing them (feat: "minor" melee attack chain cleanup) (#90080)
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.

Improving old code. And I want to be able to pet mobroaches while
holding them too.

🆑
qol: You can now interact with held mobs in more ways beside wearing
them.
/🆑
2025-04-29 18:22:44 -06: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)