mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-20 22:54:46 +00:00
## About The Pull Request Introduces a new MODule in the uplink, makes the user transperent and grants the ability to siphon light sources to recharge your suit. Ingame demonstration: https://www.youtube.com/watch?v=bhXNOAMDy4U ## Why It's Good For The Game I've been playing a ton of Splinter Cell and Intravenous recently and this random idea popped in my head. "Wouldn't it be cool if traitors could blend in the darkness to get a jump on their opponents?" Also unrelated "Wouldn't it be cool if tots had a tool to recharge their suit that didn't involve sitting in a pod for 10 minutes?" This PR introduces a new module to the uplink, the Wraith. It comes with a passive and active component. Passively it works exactly like the crew version of the cloaking module with just a couple of differences. 1) Doesn't need to be manually activated, if you lose the cloak it's regained after 5 seconds. 2) Lower stealth alpha value( how trasperent you are basically), slightly less visible than the crew version, not as good the ninja module however , I tuned it just enough so that you are more or less undetectable in the dark. The active component of the module lets you destroy stationary lights to recharge your suit power, if used on handheld or borg lights it turns them off for a minute. **Why do we need this module when we already have the stealth implant and the chameleon projector?** I can think of a few reasons. 1) MODsuits were designed to be customizible, traitor suits range between 6 to 16 TC, having to invest in a 7-8 TC item after you already bought a suit is fairly expensive. 2) This MODule would be a better fit for ambushes, as it doesn't have the *uncloaking* delay of its counterparts. It is however considerably worse if you get caught, as the cloak is disrupted on bump or damage. 3) It has better interactions with the sandbox. Lights can go out for many reasons, maybe it’s just a power outage, or some assistant broke it, or maybe it was anightmare. It leaves room for plausible deniability, adding to the paranoia. It's also not complete invisibility, if you want to stay undetected you need to lurk in the darkness, you might expand your domain, at the cost of the crew eventually wising up to your shaeneningans. Lastly, since the active component of the module uses the same proc of the saboteur handgun, I've updated the code to be a generic proc rather than a signal, to make it easier to reuse in the future. Item desc provided by NecromancerAnne. Module sprite made by Orcacora. ## Changelog 🆑 add: The Wraith Cloaking Module is now available in the uplink, costs 3 TC. code: the saboteur handgun now uses a generic proc rather than a signal /🆑 --------- Co-authored-by: Xander3359 <66163761+Xander3359@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
305 lines
11 KiB
Plaintext
305 lines
11 KiB
Plaintext
/**
|
|
* 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)
|
|
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_OBJ_DECONSTRUCT, PROC_REF(on_parent_deconstructed))
|
|
RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(on_light_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_ITEM_UI_ACTION_CLICK, PROC_REF(on_action_click))
|
|
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
|
|
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
|
RegisterSignal(parent, COMSIG_ATOM_SABOTEUR_ACT, PROC_REF(on_hit_by_saboteur))
|
|
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_parent_deleted))
|
|
|
|
/datum/component/seclite_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_ITEM_UI_ACTION_CLICK,
|
|
COMSIG_ATOM_ATTACKBY,
|
|
COMSIG_ATOM_EXAMINE,
|
|
COMSIG_ATOM_SABOTEUR_ACT,
|
|
COMSIG_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 = item_parent.add_item_action(/datum/action/item_action/toggle_seclight)
|
|
toggle_action_ref = WEAKREF(toggle_action)
|
|
|
|
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))
|
|
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
|
|
|
|
var/successful_toggle = light.toggle_light(user)
|
|
if(!successful_toggle)
|
|
return TRUE
|
|
user.balloon_alert(user, "[light.name] toggled [light.light_on ? "on":"off"]")
|
|
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_item_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_OBJ_DECONSTRUCT] 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
|
|
|
|
// Our light is gone already - Probably destroyed by whatever destroyed our parent. Just remove it.
|
|
if(QDELETED(light) || !is_light_removable)
|
|
remove_light()
|
|
return
|
|
|
|
// We were deconstructed in any other way, so we can just drop the light on the ground (which removes it via signal).
|
|
light.forceMove(source.drop_location())
|
|
|
|
/// Signal proc for [COMSIG_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_ATOM_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_REF(unscrew_light), source, user, tool)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
/// 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_ATOM_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.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.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
|
|
|
|
//turns the light off for a few seconds.
|
|
/datum/component/seclite_attachable/proc/on_hit_by_saboteur(datum/source, disrupt_duration)
|
|
. = light.on_saboteur(source, disrupt_duration)
|
|
update_light()
|
|
return .
|