mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 15:47:15 +01:00
[MIRROR] Refactors Gunlight / Helmetlight to be a component [MDB IGNORE] (#14226)
* Refactors Gunlight / Helmetlight to be a component * wew Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
This commit is contained in:
co-authored by
MrMelbert
Gandalf
parent
a1fd933f8c
commit
d2e1be321f
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* Component which allows you to attach a seclight to an item,
|
||||
* be it a piece of clothing or a tool.
|
||||
*/
|
||||
/datum/component/seclite_attachable
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
/// Whether we can remove the light with a screwdriver or not.
|
||||
var/is_light_removable = TRUE
|
||||
/// If passed, we wil simply update our item's icon_state when a light is attached.
|
||||
/// Formatted as parent_base_state-[light_icon-state]-"on"
|
||||
var/light_icon_state
|
||||
/// If passed, we will add overlays to the item when a light is attached.
|
||||
/// This is the icon file it grabs the overlay from.
|
||||
var/light_overlay_icon
|
||||
/// The state to take from the light overlay icon if supplied.
|
||||
var/light_overlay
|
||||
/// The X offset of our overlay if supplied.
|
||||
var/overlay_x = 0
|
||||
/// The Y offset of our overlay if supplied.
|
||||
var/overlay_y = 0
|
||||
|
||||
// Internal vars.
|
||||
/// A reference to the actual light that's attached.
|
||||
var/obj/item/flashlight/seclite/light
|
||||
/// A weakref to the item action we add with the light.
|
||||
var/datum/weakref/toggle_action_ref
|
||||
/// Static typecache of all lights we consider seclites (all lights we can attach).
|
||||
var/static/list/valid_lights = typecacheof(list(/obj/item/flashlight/seclite))
|
||||
|
||||
/datum/component/seclite_attachable/Initialize(
|
||||
obj/item/flashlight/seclite/starting_light,
|
||||
is_light_removable = TRUE,
|
||||
light_icon_state,
|
||||
light_overlay_icon,
|
||||
light_overlay,
|
||||
overlay_x = 0,
|
||||
overlay_y = 0,
|
||||
)
|
||||
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.is_light_removable = is_light_removable
|
||||
src.light_icon_state = light_icon_state
|
||||
src.light_overlay_icon = light_overlay_icon
|
||||
src.light_overlay = light_overlay
|
||||
src.overlay_x = overlay_x
|
||||
src.overlay_y = overlay_y
|
||||
|
||||
if(istype(starting_light))
|
||||
add_light(starting_light)
|
||||
|
||||
/datum/component/seclite_attachable/Destroy(force, silent)
|
||||
if(light)
|
||||
remove_light()
|
||||
return ..()
|
||||
|
||||
// Inheriting component allows lights to be added externally to things which already have a mount.
|
||||
/datum/component/seclite_attachable/InheritComponent(
|
||||
datum/component/seclite_attachable/new_component,
|
||||
original,
|
||||
obj/item/flashlight/seclite/starting_light,
|
||||
is_light_removable = TRUE,
|
||||
light_icon_state,
|
||||
light_overlay_icon,
|
||||
light_overlay,
|
||||
overlay_x,
|
||||
overlay_y,
|
||||
)
|
||||
|
||||
if(!original)
|
||||
return
|
||||
|
||||
src.is_light_removable = is_light_removable
|
||||
|
||||
// For the rest of these arguments, default to what already exists
|
||||
if(light_icon_state)
|
||||
src.light_icon_state = light_icon_state
|
||||
if(light_overlay_icon)
|
||||
src.light_overlay_icon = light_overlay_icon
|
||||
if(light_overlay)
|
||||
src.light_overlay = light_overlay
|
||||
if(overlay_x)
|
||||
src.overlay_x = overlay_x
|
||||
if(overlay_x)
|
||||
src.overlay_y = overlay_y
|
||||
|
||||
if(istype(starting_light))
|
||||
add_light(starting_light)
|
||||
|
||||
/datum/component/seclite_attachable/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ATOM_DESTRUCTION, .proc/on_parent_deconstructed)
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/on_light_exit)
|
||||
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), .proc/on_screwdriver)
|
||||
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, .proc/on_update_icon_state)
|
||||
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/on_update_overlays)
|
||||
RegisterSignal(parent, COMSIG_ITEM_UI_ACTION_CLICK, .proc/on_action_click)
|
||||
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attackby)
|
||||
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
|
||||
RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/on_parent_deleted)
|
||||
|
||||
/datum/component/seclite_attachable/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_ATOM_DESTRUCTION,
|
||||
COMSIG_ATOM_EXITED,
|
||||
COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
|
||||
COMSIG_ATOM_UPDATE_ICON_STATE,
|
||||
COMSIG_ATOM_UPDATE_OVERLAYS,
|
||||
COMSIG_ITEM_UI_ACTION_CLICK,
|
||||
COMSIG_PARENT_ATTACKBY,
|
||||
COMSIG_PARENT_EXAMINE,
|
||||
COMSIG_PARENT_QDELETING,
|
||||
))
|
||||
|
||||
/// Sets a new light as our current light for our parent.
|
||||
/datum/component/seclite_attachable/proc/add_light(obj/item/flashlight/new_light, mob/attacher)
|
||||
if(light)
|
||||
CRASH("[type] tried to add a new light when it already had one.")
|
||||
|
||||
light = new_light
|
||||
|
||||
light.set_light_flags(light.light_flags | LIGHT_ATTACHED)
|
||||
// We may already exist within in our parent's contents... But if we don't move it over now
|
||||
if(light.loc != parent)
|
||||
light.forceMove(parent)
|
||||
|
||||
// We already have an action for the light for some reason? Clean it up
|
||||
if(toggle_action_ref?.resolve())
|
||||
stack_trace("[type] - add_light had an existing toggle action when add_light was called.")
|
||||
QDEL_NULL(toggle_action_ref)
|
||||
|
||||
// Make a new toggle light item action for our parent
|
||||
var/obj/item/item_parent = parent
|
||||
var/datum/action/item_action/toggle_seclight/toggle_action = new(item_parent)
|
||||
toggle_action_ref = WEAKREF(toggle_action)
|
||||
if(attacher && item_parent.loc == attacher)
|
||||
toggle_action.Grant(attacher)
|
||||
|
||||
update_light()
|
||||
|
||||
/// Removes the current light from our parent.
|
||||
/datum/component/seclite_attachable/proc/remove_light()
|
||||
// Our action may be linked to our parent,
|
||||
// but it's really sourced from our light. Get rid of it.
|
||||
QDEL_NULL(toggle_action_ref)
|
||||
|
||||
// It is possible the light was removed by being deleted.
|
||||
if(!QDELETED(light))
|
||||
UnregisterSignal(light, COMSIG_PARENT_QDELETING)
|
||||
light.set_light_flags(light.light_flags & ~LIGHT_ATTACHED)
|
||||
light.update_brightness()
|
||||
|
||||
light = null
|
||||
update_light()
|
||||
|
||||
/// Toggles the light within on or off.
|
||||
/// Returns TRUE if there is a light inside, FALSE otherwise.
|
||||
/datum/component/seclite_attachable/proc/toggle_light(mob/user)
|
||||
if(!light)
|
||||
return FALSE
|
||||
|
||||
light.on = !light.on
|
||||
light.update_brightness()
|
||||
if(user)
|
||||
user.balloon_alert(user, "[light.name] toggled [light.on ? "on":"off"]")
|
||||
|
||||
playsound(light, 'sound/weapons/empty.ogg', 100, TRUE)
|
||||
update_light()
|
||||
return TRUE
|
||||
|
||||
/// Called after the a light is added, removed, or toggles.
|
||||
/// Ensures all of our appearances look correct for the new light state.
|
||||
/datum/component/seclite_attachable/proc/update_light()
|
||||
var/obj/item/item_parent = parent
|
||||
item_parent.update_appearance()
|
||||
item_parent.update_action_buttons()
|
||||
|
||||
/// Signal proc for [COMSIG_ATOM_EXITED] that handles our light being removed or deleted from our parent.
|
||||
/datum/component/seclite_attachable/proc/on_light_exit(obj/item/source, atom/movable/gone, direction)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(gone == light)
|
||||
remove_light()
|
||||
|
||||
/// Signal proc for [COMSIG_ATOM_DESTRUCTION] that drops our light to the ground if our parent is deconstructed.
|
||||
/datum/component/seclite_attachable/proc/on_parent_deconstructed(obj/item/source, disassembled)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
light.forceMove(source.drop_location())
|
||||
|
||||
/// Signal proc for [COMSIG_PARENT_QDELETING] that deletes our light if our parent is deleted.
|
||||
/datum/component/seclite_attachable/proc/on_parent_deleted(obj/item/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
QDEL_NULL(light)
|
||||
|
||||
/// Signal proc for [COMSIG_ITEM_UI_ACTION_CLICK] that toggles our light on and off if our action button is clicked.
|
||||
/datum/component/seclite_attachable/proc/on_action_click(obj/item/source, mob/user, datum/action)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// This isn't OUR action specifically, we don't care.
|
||||
if(!IS_WEAKREF_OF(action, toggle_action_ref))
|
||||
return
|
||||
|
||||
// Toggle light fails = no light attached = shouldn't be possible
|
||||
if(!toggle_light(user))
|
||||
CRASH("[type] - on_action_click somehow both HAD AN ACTION and also HAD A TRIGGERABLE ACTION, without having an attached light.")
|
||||
|
||||
return COMPONENT_ACTION_HANDLED
|
||||
|
||||
/// Signal proc for [COMSIG_PARENT_ATTACKBY] that allows a user to attach a seclite by hitting our parent with it.
|
||||
/datum/component/seclite_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, params)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!is_type_in_typecache(attacking_item, valid_lights))
|
||||
return
|
||||
|
||||
if(light)
|
||||
source.balloon_alert(attacker, "already has \a [light]!")
|
||||
return
|
||||
|
||||
if(!attacker.transferItemToLoc(attacking_item, source))
|
||||
return
|
||||
|
||||
add_light(attacking_item, attacker)
|
||||
source.balloon_alert(attacker, "attached [attacking_item]")
|
||||
return COMPONENT_NO_AFTERATTACK
|
||||
|
||||
/// Signal proc for [COMSIG_ATOM_TOOL_ACT] via [TOOL_SCREWDRIVER] that removes any attached seclite.
|
||||
/datum/component/seclite_attachable/proc/on_screwdriver(obj/item/source, mob/user, obj/item/tool)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!light || !is_light_removable)
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, .proc/unscrew_light, source, user, tool)
|
||||
return COMPONENT_BLOCK_TOOL_ATTACK
|
||||
|
||||
/// Invoked asyncronously from [proc/on_screwdriver]. Handles removing the light from our parent.
|
||||
/datum/component/seclite_attachable/proc/unscrew_light(obj/item/source, mob/user, obj/item/tool)
|
||||
tool?.play_tool_sound(source)
|
||||
source.balloon_alert(user, "unscrewed [light]")
|
||||
|
||||
var/obj/item/flashlight/seclite/to_remove = light
|
||||
|
||||
// The forcemove here will call exited on the light, and automatically update our references / etc
|
||||
to_remove.forceMove(source.drop_location())
|
||||
if(source.Adjacent(user) && !issilicon(user))
|
||||
user.put_in_hands(to_remove)
|
||||
|
||||
/// Signal proc for [COMSIG_PARENT_EXAMINE] that shows our item can have / does have a seclite attached.
|
||||
/datum/component/seclite_attachable/proc/on_examine(obj/item/source, mob/examiner, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(light)
|
||||
examine_list += "It has \a [light] [is_light_removable ? "mounted on it with a few <b>screws</b>" : "permanently mounted on it"]."
|
||||
else
|
||||
examine_list += "It has a mounting point for a <b>seclite</b>."
|
||||
|
||||
/// Signal proc for [COMSIG_ATOM_UPDATE_OVERLAYS] that updates our parent with our seclite overlays, if we have some.
|
||||
/datum/component/seclite_attachable/proc/on_update_overlays(obj/item/source, list/overlays)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// No overlays to add, no reason to run
|
||||
if(!light_overlay || !light_overlay_icon)
|
||||
return
|
||||
// No light, nothing to add
|
||||
if(!light)
|
||||
return
|
||||
|
||||
var/overlay_state = "[light_overlay][light.on ? "_on":""]"
|
||||
var/mutable_appearance/flashlight_overlay = mutable_appearance(light_overlay_icon, overlay_state)
|
||||
flashlight_overlay.pixel_x = overlay_x
|
||||
flashlight_overlay.pixel_y = overlay_y
|
||||
overlays += flashlight_overlay
|
||||
|
||||
/// Signal proc for [COMSIG_ATOM_UPDATE_ICON_STATE] that updates our parent's icon state, if we have one.
|
||||
/datum/component/seclite_attachable/proc/on_update_icon_state(obj/item/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// No icon state to set, no reason to run
|
||||
if(!light_icon_state)
|
||||
return
|
||||
|
||||
// Get the "base icon state" to work on
|
||||
var/base_state = source.base_icon_state || initial(source.icon_state)
|
||||
// Updates our icon state based on our light state.
|
||||
if(light)
|
||||
source.icon_state = "[base_state]-[light_icon_state][light.on ? "-on":""]"
|
||||
|
||||
// Reset their icon state when if we've got no light.
|
||||
else if(source.icon_state != base_state)
|
||||
// Yes, this might mess with other icon state alterations,
|
||||
// but that's the downside of using icon states over overlays.
|
||||
source.icon_state = base_state
|
||||
Reference in New Issue
Block a user