diff --git a/code/__DEFINES/dcs/signals/signals_saboteur.dm b/code/__DEFINES/dcs/signals/signals_saboteur.dm
new file mode 100644
index 00000000000..5ef536683fe
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_saboteur.dm
@@ -0,0 +1,4 @@
+// Light disruptor. Not to be confused with the light eater, which permanently disables lights.
+
+/// from /obj/projectile/energy/fisher/on_hit() or /obj/item/gun/energy/recharge/fisher when striking a target
+#define COMSIG_HIT_BY_SABOTEUR "hit_by_saboteur"
diff --git a/code/datums/components/seclight_attachable.dm b/code/datums/components/seclight_attachable.dm
index 56761cfd1bf..b3f36fe041a 100644
--- a/code/datums/components/seclight_attachable.dm
+++ b/code/datums/components/seclight_attachable.dm
@@ -98,6 +98,7 @@
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_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
/datum/component/seclite_attachable/UnregisterFromParent()
UnregisterSignal(parent, list(
@@ -156,12 +157,10 @@
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)
+ var/successful_toggle = light.toggle_light(user)
+ if(!successful_toggle)
+ return TRUE
+ user.balloon_alert(user, "[light.name] toggled [light.on ? "on":"off"]")
update_light()
return TRUE
@@ -296,3 +295,9 @@
// 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
+
+/// Signal proc for [COMSIG_HIT_BY_SABOTEUR] that turns the light off for a few seconds.
+/datum/component/seclite_attachable/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ . = light.on_saboteur(source, disrupt_duration)
+ update_light()
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index a47115a9f60..575464a98af 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -30,7 +30,6 @@
var/busy = FALSE
var/emped = FALSE //Number of consecutive EMP's on this camera
var/in_use_lights = 0
-
// Upgrades bitflag
var/upgrades = 0
@@ -105,6 +104,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
alarm_manager = new(src)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
+
/obj/machinery/camera/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
for(var/i in network)
network -= i
@@ -155,7 +156,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
if(!status && powered())
. += span_info("It can reactivated with wirecutters.")
-/obj/machinery/camera/emp_act(severity)
+/obj/machinery/camera/emp_act(severity, reset_time = 90 SECONDS)
. = ..()
if(!status)
return
@@ -168,7 +169,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
set_light(0)
emped = emped+1 //Increase the number of consecutive EMP's
update_appearance()
- addtimer(CALLBACK(src, PROC_REF(post_emp_reset), emped, network), 90 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(post_emp_reset), emped, network), reset_time)
for(var/i in GLOB.player_list)
var/mob/M = i
if (M.client?.eye == src)
@@ -176,6 +177,11 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
M.reset_perspective(null)
to_chat(M, span_warning("The screen bursts into static!"))
+/obj/machinery/camera/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ emp_act(EMP_LIGHT, reset_time = disrupt_duration)
+ return TRUE
+
/obj/machinery/camera/proc/post_emp_reset(thisemp, previous_network)
if(QDELETED(src))
return
@@ -188,7 +194,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
if(can_use())
GLOB.cameranet.addCamera(src)
emped = 0 //Resets the consecutive EMP count
- addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), 100)
+ addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), 10 SECONDS)
/obj/machinery/camera/ex_act(severity, target)
if(invuln)
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 14fc91ea7d0..ee7a66044fd 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -23,6 +23,8 @@
light_range = 4
light_power = 1
light_on = FALSE
+ /// If we've been forcibly disabled for a temporary amount of time.
+ COOLDOWN_DECLARE(disabled_time)
/// Can we toggle this light on and off (used for contexual screentips only)
var/toggle_context = TRUE
/// The sound the light makes when it's turned on
@@ -32,16 +34,15 @@
/// Is the light turned on or off currently
var/on = FALSE
-// SKYRAT EDIT REMOVAL BEGIN - MOVED TO MODUALR FLASHLIGHT.DM
-/*
/obj/item/flashlight/Initialize(mapload)
. = ..()
if(icon_state == "[initial(icon_state)]-on")
on = TRUE
update_brightness()
register_context()
-*/
-// SKYRAT EDIT REMOVAL END
+
+ if(toggle_context)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
/obj/item/flashlight/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
// single use lights can be toggled on once
@@ -68,19 +69,21 @@
if(light_system == STATIC_LIGHT)
update_light()
-// SKYRAT EDIT REMOVAL BEGIN - MOVED TO MODUALR FLASHLIGHT.DM
-/*
-/obj/item/flashlight/proc/toggle_light()
+/obj/item/flashlight/proc/toggle_light(mob/user)
+ var/disrupted = FALSE
+ //playsound(src, on ? sound_on : sound_off, 40, TRUE) SKYRAT EDIT REMOVAL - SOUND HANDLED IN MODULAR FLASHLIGHT.DM
on = !on
- playsound(src, on ? sound_on : sound_off, 40, TRUE)
+ if(!COOLDOWN_FINISHED(src, disabled_time))
+ if(user)
+ balloon_alert(user, "disrupted!")
+ on = FALSE
+ disrupted = TRUE
update_brightness()
update_item_action_buttons()
- return TRUE
+ return !disrupted
/obj/item/flashlight/attack_self(mob/user)
- toggle_light()
-*/
-// SKYRAT EDIT REMOVAL END
+ toggle_light(user)
/obj/item/flashlight/attack_hand_secondary(mob/user, list/modifiers)
attack_self(user)
@@ -262,6 +265,14 @@
if(istype(user) && dir != user.dir)
setDir(user.dir)
+/// when hit by a light disruptor - turns the light off, forces the light to be disabled for a few seconds
+/obj/item/flashlight/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(on)
+ toggle_light()
+ COOLDOWN_START(src, disabled_time, disrupt_duration)
+ return TRUE
+
/obj/item/flashlight/pen
name = "penlight"
desc = "A pen-sized light, used by medical staff. It can also be used to create a hologram to alert people of incoming medical assistance."
@@ -420,16 +431,15 @@
damtype = BURN
. = ..()
-/obj/item/flashlight/flare/turn_off() //SKYRAT EDIT CHANGE
- //on = FALSE SKYRAT EDIT REMOVAL
+/obj/item/flashlight/flare/proc/turn_off()
+ on = FALSE
name = initial(name)
attack_verb_continuous = initial(attack_verb_continuous)
attack_verb_simple = initial(attack_verb_simple)
hitsound = initial(hitsound)
force = initial(force)
damtype = initial(damtype)
- //update_brightness() - SKYRAT EDIT MOVED TO PARENT
- . = ..() //SKYRAT EDIT - MODULAR PARENT PROC
+ update_brightness()
/obj/item/flashlight/flare/extinguish()
. = ..()
@@ -749,11 +759,9 @@
turn_off()
STOP_PROCESSING(SSobj, src)
-/* SKYRAT EDIT REMOVAL
/obj/item/flashlight/glowstick/proc/turn_off()
on = FALSE
update_appearance(UPDATE_ICON)
-*/
/obj/item/flashlight/glowstick/update_appearance(updates=ALL)
. = ..()
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 1e05bae6298..de19ce19058 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -15,7 +15,7 @@
contents_pressure_protection = 0
/// How insulated the thing is, for the purposes of calculating body temperature. Must be between 0 and 1!
contents_thermal_insulation = 0
- pass_flags_self = LETPASSCLICKS
+ pass_flags_self = PASSSTRUCTURE | LETPASSCLICKS
/// The overlay for the closet's door
var/obj/effect/overlay/closet_door/door_obj
diff --git a/code/modules/cargo/markets/market_items/weapons.dm b/code/modules/cargo/markets/market_items/weapons.dm
index f40e4fa1447..6115b1e1557 100644
--- a/code/modules/cargo/markets/market_items/weapons.dm
+++ b/code/modules/cargo/markets/market_items/weapons.dm
@@ -63,3 +63,13 @@
price_max = CARGO_CRATE_VALUE * 2
stock_max = 2
availability_prob = 50
+
+/datum/market_item/weapon/fisher
+ name = "SC/FISHER Saboteur Handgun"
+ desc = "A self-recharging, compact pistol that disrupts flashlights and security cameras, if only temporarily. Also usable in melee."
+ item = /obj/item/gun/energy/recharge/fisher
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ stock_max = 1
+ availability_prob = 50
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index a1edd69ba3e..8ae842807a8 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -38,6 +38,7 @@
)
//technically it's huge and bulky, but this provides an incentive to use it
AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=20)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
/obj/item/kinetic_crusher/Destroy()
QDEL_LIST(trophies)
@@ -165,6 +166,10 @@
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
update_appearance()
+/obj/item/kinetic_crusher/proc/on_saboteur(datum/source, disrupt_duration)
+ set_light_on(FALSE)
+ playsound(src, 'sound/weapons/empty.ogg', 100, TRUE)
+ return TRUE
/obj/item/kinetic_crusher/update_icon_state()
inhand_icon_state = "crusher[HAS_TRAIT(src, TRAIT_WIELDED)]" // this is not icon_state and not supported by 2hcomponent
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index 9546a659e41..f087eb6b92c 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -68,6 +68,7 @@
RegisterSignal(ethereal, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag_act))
RegisterSignal(ethereal, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act))
RegisterSignal(ethereal, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
+ RegisterSignal(ethereal, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
ethereal_light = ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species)
spec_updatehealth(ethereal)
new_ethereal.set_safe_hunger_level()
@@ -84,6 +85,7 @@
UnregisterSignal(former_ethereal, COMSIG_ATOM_EMAG_ACT)
UnregisterSignal(former_ethereal, COMSIG_ATOM_EMP_ACT)
UnregisterSignal(former_ethereal, COMSIG_LIGHT_EATER_ACT)
+ UnregisterSignal(former_ethereal, COMSIG_HIT_BY_SABOTEUR)
QDEL_NULL(ethereal_light)
return ..()
@@ -147,6 +149,15 @@
if(EMP_HEAVY)
addtimer(CALLBACK(src, PROC_REF(stop_emp), H), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds
+/datum/species/ethereal/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ var/mob/living/carbon/human/our_target = source
+ EMPeffect = TRUE
+ spec_updatehealth(our_target)
+ to_chat(our_target, span_warning("Something inside of you crackles in a bad way."))
+ our_target.take_bodypart_damage(burn = 3, wound_bonus = CANT_WOUND)
+ addtimer(CALLBACK(src, PROC_REF(stop_emp), our_target), disrupt_duration, TIMER_UNIQUE|TIMER_OVERRIDE)
+
/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/H, mob/user)
SIGNAL_HANDLER
if(emageffect)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 9732222f0cf..17e59660eb4 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -19,6 +19,7 @@
AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg)
RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge))
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
robot_modules_background = new()
robot_modules_background.icon_state = "block"
@@ -470,6 +471,13 @@
smash_headlamp()
return COMPONENT_BLOCK_LIGHT_EATER
+/// special handling for getting shot with a light disruptor/saboteur e.g. the fisher
+/mob/living/silicon/robot/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(lamp_enabled)
+ toggle_headlamp(TRUE)
+ to_chat(src, span_warning("Your headlamp was forcibly turned off. Restarting it should fix it, though."))
+ return TRUE
/**
* Handles headlamp smashing
diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm
index 3ab8ece250f..0898e31735b 100644
--- a/code/modules/power/floodlight.dm
+++ b/code/modules/power/floodlight.dm
@@ -144,6 +144,7 @@
/obj/machinery/power/floodlight/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_OBJ_PAINTED, TYPE_PROC_REF(/obj/machinery/power/floodlight, on_color_change)) //update light color when color changes
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
register_context()
/obj/machinery/power/floodlight/proc/on_color_change(obj/machinery/power/flood_light, mob/user, obj/item/toy/crayon/spraycan/spraycan, is_dark_color)
@@ -289,6 +290,11 @@
/obj/machinery/power/floodlight/attack_ai(mob/user)
return attack_hand(user)
+/obj/machinery/power/floodlight/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ atom_break(ENERGY) // technically,
+ return TRUE
+
/obj/machinery/power/floodlight/atom_break(damage_flag)
. = ..()
if(!.)
@@ -297,7 +303,8 @@
var/obj/structure/floodlight_frame/floodlight_frame = new(loc)
floodlight_frame.state = FLOODLIGHT_NEEDS_LIGHTS
- new /obj/item/light/tube(loc)
+ var/obj/item/light/tube/our_light = new(loc)
+ our_light.shatter()
qdel(src)
diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm
index 2486a6500c1..ea6a28a8fe4 100644
--- a/code/modules/power/lighting/light.dm
+++ b/code/modules/power/lighting/light.dm
@@ -114,6 +114,7 @@
// Light projects out backwards from the dir of the light
set_light(l_dir = REVERSE_DIR(dir))
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
AddElement(/datum/element/atmos_sensitive, mapload)
return INITIALIZE_HINT_LATELOAD
@@ -706,6 +707,11 @@
tube?.burn()
return
+/obj/machinery/light/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ break_light_tube()
+ return TRUE
+
/obj/machinery/light/proc/grey_tide(datum/source, list/grey_tide_areas)
SIGNAL_HANDLER
diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm
index bedfab7b235..24fba4b9ba4 100644
--- a/code/modules/projectiles/ammunition/energy/special.dm
+++ b/code/modules/projectiles/ammunition/energy/special.dm
@@ -74,3 +74,9 @@
select_name = "marksman nanoshot"
e_cost = 0
fire_sound = 'sound/weapons/gun/revolver/shot_alt.ogg'
+
+/obj/item/ammo_casing/energy/fisher
+ projectile_type = /obj/projectile/energy/fisher
+ select_name = "light-buster"
+ e_cost = 250
+ fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // fwip fwip fwip fwip
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index ddacb419bbe..e76af741a18 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -26,6 +26,7 @@
var/vary_fire_sound = TRUE
var/fire_sound_volume = 50
var/dry_fire_sound = 'sound/weapons/gun/general/dry_fire.ogg'
+ var/dry_fire_sound_volume = 30
var/suppressed = null //whether or not a message is displayed when fired
var/can_suppress = FALSE
var/suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg'
@@ -169,7 +170,7 @@
/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj)
balloon_alert_to_viewers("*click*")
- playsound(src, dry_fire_sound, 30, TRUE)
+ playsound(src, dry_fire_sound, dry_fire_sound_volume, TRUE)
/obj/item/gun/proc/fire_sounds()
if(suppressed)
diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm
index 6644accd8a5..eed27478755 100644
--- a/code/modules/projectiles/guns/energy/recharge.dm
+++ b/code/modules/projectiles/guns/energy/recharge.dm
@@ -135,3 +135,36 @@
custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*2)
suppressed = null
ammo_type = list(/obj/item/ammo_casing/energy/bolt/large)
+
+/// A silly gun that does literally zero damage, but disrupts electrical sources of light, like flashlights.
+/obj/item/gun/energy/recharge/fisher
+ name = "\improper SC/FISHER disruptor"
+ desc = "A self-recharging, permanently suppressed, and very haphazardly modified accelerator handgun that does literally nothing to anything except light fixtures and cameras. \
+ Can fire twice before requiring a recharge, with bolts passing through machinery, but demands precision."
+ icon_state = "fisher"
+ base_icon_state = "fisher"
+ dry_fire_sound_volume = 10
+ w_class = WEIGHT_CLASS_SMALL
+ holds_charge = TRUE
+ suppressed = TRUE
+ recharge_time = 1.2 SECONDS
+ ammo_type = list(/obj/item/ammo_casing/energy/fisher)
+
+/obj/item/gun/energy/recharge/fisher/examine_more(mob/user)
+ . = ..()
+ . += span_notice("The SC/FISHER is an illegally-modified kinetic accelerator cut down and refit into a disassembled miniature energy gun chassis, with its pressure chamber \
+ attenuated to launch kinetic bolts that disrupt flashlights and cameras, if only temporarily. This effect also works on cyborg headlamps, and works longer in melee.
\
+ While some would argue that this is a really terrible design choice, others argue that it is very funny to be able to shoot at light sources. Caveat emptor.")
+
+/obj/item/gun/energy/recharge/fisher/afterattack(atom/target, mob/living/user, flag, params)
+ // you should just shoot them, but in case you can't/wont
+ . = ..()
+ if(user.Adjacent(target))
+ var/obj/projectile/energy/fisher/melee/simulated_hit = new
+ simulated_hit.on_hit(target)
+
+/obj/item/gun/energy/recharge/fisher/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ // ...you reeeeeally just shoot them, but in case you can't/won't
+ . = ..()
+ var/obj/projectile/energy/fisher/melee/simulated_hit = new
+ simulated_hit.on_hit(hit_atom)
diff --git a/code/modules/projectiles/projectile/special/lightbreaker.dm b/code/modules/projectiles/projectile/special/lightbreaker.dm
new file mode 100644
index 00000000000..cf273a1d3b2
--- /dev/null
+++ b/code/modules/projectiles/projectile/special/lightbreaker.dm
@@ -0,0 +1,34 @@
+/obj/projectile/energy/fisher
+ name = "attenuated kinetic force"
+ alpha = 0
+ damage = 0
+ damage_type = BRUTE
+ armor_flag = BOMB
+ range = 14
+ projectile_phasing = PASSTABLE | PASSMOB | PASSMACHINE | PASSSTRUCTURE
+ hitscan = TRUE
+ var/disrupt_duration = 10 SECONDS
+
+/obj/projectile/energy/fisher/on_hit(atom/target, blocked, pierce_hit)
+ . = ..()
+ SEND_SIGNAL(target, COMSIG_HIT_BY_SABOTEUR, disrupt_duration)
+ if(!isliving(target))
+ return
+ var/list/things_to_disrupt = list()
+ var/lights_flickered = 0
+ if(ishuman(target))
+ var/mob/living/carbon/human/human_target = target
+ things_to_disrupt = human_target.get_all_gear()
+ else
+ var/mob/living/living_target = target // i guess this covers borgs too?
+ things_to_disrupt = living_target.get_equipped_items(include_pockets = TRUE, include_accessories = TRUE)
+ for(var/obj/item/thingy as anything in things_to_disrupt)
+ if(SEND_SIGNAL(thingy, COMSIG_HIT_BY_SABOTEUR, disrupt_duration))
+ lights_flickered++
+ if(lights_flickered)
+ to_chat(target, span_warning("Your light [lights_flickered > 1 ? "sources flick" : "source flicks"] off."))
+
+/obj/projectile/energy/fisher/melee
+ range = 1
+ suppressed = SUPPRESSED_VERY
+ disrupt_duration = 20 SECONDS
diff --git a/icons/obj/weapons/guns/energy.dmi b/icons/obj/weapons/guns/energy.dmi
index 07ac342a36d..97b75335b91 100644
Binary files a/icons/obj/weapons/guns/energy.dmi and b/icons/obj/weapons/guns/energy.dmi differ
diff --git a/modular_skyrat/modules/cell_component/code/flashlight.dm b/modular_skyrat/modules/cell_component/code/flashlight.dm
index 01de50266e3..6e585781a83 100644
--- a/modular_skyrat/modules/cell_component/code/flashlight.dm
+++ b/modular_skyrat/modules/cell_component/code/flashlight.dm
@@ -18,13 +18,10 @@
/obj/item/flashlight/Initialize(mapload)
. = ..()
- if(icon_state == "[initial(icon_state)]-on")
- turn_on(FALSE)
- update_brightness()
- register_context()
+ update_item_action_buttons()
if(uses_battery)
- AddComponent(/datum/component/cell, cell_override, CALLBACK(src, PROC_REF(turn_off)), _has_cell_overlays = FALSE)
+ AddComponent(/datum/component/cell, cell_override, CALLBACK(src, PROC_REF(quietly_turn_off)), _has_cell_overlays = FALSE)
/obj/item/flashlight/examine(mob/user)
. = ..()
@@ -55,46 +52,37 @@
set_light_power(initial(light_power))
to_chat(user, span_notice("You set [src] to low."))
-/obj/item/flashlight/proc/toggle_light()
+/obj/item/flashlight/toggle_light(noisy = TRUE)
+ . = ..()
if(on)
- turn_off()
- else
+ after_turn_on()
if(uses_battery && !(item_use_power(power_use_amount, TRUE) & COMPONENT_POWER_SUCCESS))
return
- turn_on(makes_noise_when_lit)
- playsound(src, on ? sound_on : sound_off, 40, TRUE)
- return TRUE
-
-/obj/item/flashlight/attack_self(mob/user)
- . = ..()
- toggle_light()
+ if(noisy)
+ playsound(src, on ? sound_on : sound_off, 40, TRUE)
/**
* Handles turning on the flashlight.
* Parameters:
* * noisy - Boolean on whether the flashlight should make an additional noise from being turned on or not. Defaults to TRUE.
*/
-/obj/item/flashlight/proc/turn_on(noisy = TRUE)
- on = TRUE
- if (uses_battery)
+/obj/item/flashlight/proc/after_turn_on(noisy = TRUE)
+ if(uses_battery)
START_PROCESSING(SSobj, src)
- update_brightness()
if(noisy)
playsound(src, 'modular_skyrat/master_files/sound/effects/flashlight.ogg', 40, TRUE) //Credits to ERIS for the sound
- update_item_action_buttons()
-/// Handles turning off the flashlight.
-/obj/item/flashlight/proc/turn_off()
- on = FALSE
- update_brightness()
- update_item_action_buttons()
+/// Quietly turns the flashlight off if it was on (called by the battery running out of charge).
+/obj/item/flashlight/proc/quietly_turn_off()
+ if(on)
+ toggle_light(noisy = FALSE)
/obj/item/flashlight/process(seconds_per_tick)
if(!on)
STOP_PROCESSING(SSobj, src)
return
if(uses_battery && !(item_use_power(power_use_amount) & COMPONENT_POWER_SUCCESS))
- turn_off()
+ quietly_turn_off()
/obj/item/flashlight/update_icon_state()
. = ..()
diff --git a/tgstation.dme b/tgstation.dme
index 194d81879f0..d1c22afce1e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -319,6 +319,7 @@
#include "code\__DEFINES\dcs\signals\signals_radiation.dm"
#include "code\__DEFINES\dcs\signals\signals_reagent.dm"
#include "code\__DEFINES\dcs\signals\signals_restaurant.dm"
+#include "code\__DEFINES\dcs\signals\signals_saboteur.dm"
#include "code\__DEFINES\dcs\signals\signals_scangate.dm"
#include "code\__DEFINES\dcs\signals\signals_screentips.dm"
#include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm"
@@ -4991,6 +4992,7 @@
#include "code\modules\projectiles\projectile\special\floral.dm"
#include "code\modules\projectiles\projectile\special\gravity.dm"
#include "code\modules\projectiles\projectile\special\ion.dm"
+#include "code\modules\projectiles\projectile\special\lightbreaker.dm"
#include "code\modules\projectiles\projectile\special\meteor.dm"
#include "code\modules\projectiles\projectile\special\mindflayer.dm"
#include "code\modules\projectiles\projectile\special\neurotoxin.dm"