diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index fb6fb43857c..b093e620c37 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -285,6 +285,9 @@
// /obj/item/gun signals
+///called in /obj/item/gun/fire_gun (user, target, flag, params)
+#define COMSIG_GUN_TRY_FIRE "gun_try_fire"
+ #define COMPONENT_CANCEL_GUN_FIRE (1<<0)
///called in /obj/item/gun/process_fire (src, target, params, zone_override)
#define COMSIG_MOB_FIRED_GUN "mob_fired_gun"
///called in /obj/item/gun/process_fire (user, target, params, zone_override)
diff --git a/code/datums/components/scope.dm b/code/datums/components/scope.dm
new file mode 100644
index 00000000000..693028a823c
--- /dev/null
+++ b/code/datums/components/scope.dm
@@ -0,0 +1,178 @@
+/datum/component/scope
+ /// How far we can extend, with modifier of 1, up to our vision edge, higher numbers multiply.
+ var/range_modifier = 1
+ /// Fullscreen object we use for tracking the shots.
+ var/atom/movable/screen/fullscreen/scope/tracker
+
+/datum/component/scope/Initialize(range_modifier)
+ if(!isgun(parent))
+ return COMPONENT_INCOMPATIBLE
+ src.range_modifier = range_modifier
+
+/datum/component/scope/Destroy(force, silent)
+ if(tracker)
+ stop_zooming(tracker.marksman)
+ return ..()
+
+/datum/component/scope/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move)
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK_SECONDARY, .proc/on_secondary_afterattack)
+ RegisterSignal(parent, COMSIG_GUN_TRY_FIRE, .proc/on_gun_fire)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+/datum/component/scope/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_ITEM_AFTERATTACK_SECONDARY,
+ COMSIG_GUN_TRY_FIRE,
+ COMSIG_PARENT_EXAMINE,
+ ))
+
+/datum/component/scope/process(delta_time)
+ if(!tracker.marksman.client)
+ stop_zooming(tracker.marksman)
+ return
+ if(!length(tracker.marksman.client.keys_held & tracker.marksman.client.movement_keys))
+ tracker.marksman.face_atom(tracker.given_turf)
+ animate(tracker.marksman.client, 0.2 SECONDS, easing = SINE_EASING, flags = EASE_OUT, pixel_x = tracker.given_x, pixel_y = tracker.given_y)
+
+/datum/component/scope/proc/on_move(atom/movable/source, atom/oldloc, dir, forced)
+ SIGNAL_HANDLER
+
+ if(!tracker)
+ return
+ stop_zooming(tracker.marksman)
+
+/datum/component/scope/proc/on_secondary_afterattack(datum/source, atom/target, mob/user, proximity_flag, click_parameters)
+ SIGNAL_HANDLER
+
+ if(tracker)
+ stop_zooming(user)
+ else
+ start_zooming(user)
+ return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN
+
+/datum/component/scope/proc/on_gun_fire(obj/item/gun/source, mob/living/user, atom/target, flag, params)
+ SIGNAL_HANDLER
+
+ if(!tracker?.given_turf || target == get_target(tracker.given_turf))
+ return NONE
+ INVOKE_ASYNC(source, /obj/item/gun.proc/fire_gun, get_target(tracker.given_turf), user)
+ return COMPONENT_CANCEL_GUN_FIRE
+
+/datum/component/scope/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += span_notice("You can scope in with right-click.")
+
+/**
+ * We find and return the best target to hit on a given turf.
+ *
+ * Arguments:
+ * * target_turf: The turf we are looking for targets on.
+*/
+/datum/component/scope/proc/get_target(turf/target_turf)
+ var/list/object_targets = list()
+ var/list/non_dense_targets = list()
+ for(var/atom/movable/possible_target in target_turf)
+ if(possible_target.layer <= PROJECTILE_HIT_THRESHHOLD_LAYER)
+ continue
+ if(possible_target.invisibility > tracker.marksman.see_invisible)
+ continue
+ if(!possible_target.mouse_opacity)
+ continue
+ if(iseffect(possible_target))
+ continue
+ if(ismob(possible_target))
+ return possible_target
+ if(!possible_target.density)
+ non_dense_targets += possible_target
+ continue
+ object_targets += possible_target
+ for(var/obj/important_object as anything in object_targets)
+ return important_object
+ for(var/obj/unimportant_object as anything in non_dense_targets)
+ return unimportant_object
+ return target_turf
+
+/**
+ * We start zooming by hiding the mouse pointer, adding our tracker overlay and starting our processing.
+ *
+ * Arguments:
+ * * user: The mob we are starting zooming on.
+*/
+/datum/component/scope/proc/start_zooming(mob/user)
+ if(!user.client)
+ return
+ user.client.mouse_override_icon = 'icons/effects/mouse_pointers/scope_hide.dmi'
+ user.update_mouse_pointer()
+ user.playsound_local(parent, 'sound/weapons/scope.ogg', 75, TRUE)
+ tracker = user.overlay_fullscreen("scope", /atom/movable/screen/fullscreen/scope, 0)
+ tracker.range_modifier = range_modifier
+ tracker.marksman = user
+ tracker.RegisterSignal(user, COMSIG_MOVABLE_MOVED, /atom/movable/screen/fullscreen/scope.proc/on_move)
+ RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, .proc/stop_zooming)
+ START_PROCESSING(SSfastprocess, src)
+
+/**
+ * We stop zooming, canceling processing, resetting stuff back to normal and deleting our tracker.
+ *
+ * Arguments:
+ * * user: The mob we are canceling zooming on.
+*/
+/datum/component/scope/proc/stop_zooming(mob/user)
+ SIGNAL_HANDLER
+
+ STOP_PROCESSING(SSfastprocess, src)
+ UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
+ if(user.client)
+ animate(user.client, 0.2 SECONDS, pixel_x = 0, pixel_y = 0)
+ user.client.mouse_override_icon = null
+ user.update_mouse_pointer()
+ user.playsound_local(parent, 'sound/weapons/scope.ogg', 75, TRUE, frequency = -1)
+ tracker = null
+ user.clear_fullscreen("scope")
+
+/atom/movable/screen/fullscreen/scope
+ icon_state = "scope"
+ plane = HUD_PLANE
+ mouse_opacity = MOUSE_OPACITY_ICON
+ /// Multiplier for given_X an given_y.
+ var/range_modifier = 1
+ /// The mob the scope is on.
+ var/mob/marksman
+ /// Pixel x we send to the scope component.
+ var/given_x = 0
+ /// Pixel y we send to the scope component.
+ var/given_y = 0
+ /// The turf we send to the scope component.
+ var/turf/given_turf
+ /// The coordinate on our mouseentered, for performance reasons.
+ COOLDOWN_DECLARE(coordinate_cooldown)
+
+/atom/movable/screen/fullscreen/scope/proc/on_move(atom/source, atom/oldloc, dir, forced)
+ SIGNAL_HANDLER
+
+ if(!given_turf)
+ return
+ var/x_offset = source.loc.x - oldloc.x
+ var/y_offset = source.loc.y - oldloc.y
+ given_turf = locate(given_turf.x+x_offset, given_turf.y+y_offset, given_turf.z)
+
+/atom/movable/screen/fullscreen/scope/MouseEntered(location, control, params)
+ . = ..()
+ MouseMove(location, control, params)
+
+/atom/movable/screen/fullscreen/scope/MouseMove(location, control, params)
+ if(!marksman?.client || usr != marksman)
+ return
+ if(!COOLDOWN_FINISHED(src, coordinate_cooldown))
+ return
+ COOLDOWN_START(src, coordinate_cooldown, 0.2 SECONDS)
+ var/list/modifiers = params2list(params)
+ var/icon_x = text2num(LAZYACCESS(modifiers, VIS_X))
+ var/icon_y = text2num(LAZYACCESS(modifiers, VIS_Y))
+ var/list/view = getviewsize(marksman.client.view)
+ given_x = round(range_modifier * (icon_x - view[1]*world.icon_size/2))
+ given_y = round(range_modifier * (icon_y - view[2]*world.icon_size/2))
+ given_turf = locate(marksman.x+round(given_x/world.icon_size, 1),marksman.y+round(given_y/world.icon_size, 1),marksman.z)
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm
index c504bb4f7b2..d5f37cca4c9 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm
@@ -44,3 +44,6 @@
/obj/effect/projectile/impact/solar
name = "solar impact"
icon_state = "impact_solar"
+
+/obj/effect/projectile/impact/sniper
+ icon_state = "sniper"
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm
index a26141d80a8..65f0ba39767 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm
@@ -35,3 +35,6 @@
/obj/effect/projectile/muzzle/solar
icon_state = "muzzle_solar"
+
+/obj/effect/projectile/muzzle/sniper
+ icon_state = "sniper"
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
index cb1334aadca..05777826105 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
@@ -73,3 +73,6 @@
/obj/effect/projectile/tracer/laser/emitter
name = "emitter beam"
icon_state = "emitter"
+
+/obj/effect/projectile/tracer/sniper
+ icon_state = "sniper"
diff --git a/code/modules/antagonists/heretic/items/hunter_rifle.dm b/code/modules/antagonists/heretic/items/hunter_rifle.dm
index c43dbce8f93..d12ff4236ea 100644
--- a/code/modules/antagonists/heretic/items/hunter_rifle.dm
+++ b/code/modules/antagonists/heretic/items/hunter_rifle.dm
@@ -10,9 +10,10 @@
worn_icon_state = "moistprime"
mag_type = /obj/item/ammo_box/magazine/internal/boltaction/lionhunter
fire_sound = 'sound/weapons/gun/sniper/shot.ogg'
- zoomable = TRUE
- zoom_amt = 5
- zoom_out_amt = 3
+
+/obj/item/gun/ballistic/rifle/lionhunter/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/scope, range_modifier = 1.25)
/obj/item/ammo_box/magazine/internal/boltaction/lionhunter
name = "lionhunter rifle internal magazine"
diff --git a/code/modules/projectiles/ammunition/ballistic/sniper.dm b/code/modules/projectiles/ammunition/ballistic/sniper.dm
index 3c191509cf7..3749520a522 100644
--- a/code/modules/projectiles/ammunition/ballistic/sniper.dm
+++ b/code/modules/projectiles/ammunition/ballistic/sniper.dm
@@ -18,3 +18,8 @@
name = ".50 penetrator round bullet casing"
desc = "A .50 caliber penetrator round casing."
projectile_type = /obj/projectile/bullet/p50/penetrator
+
+/obj/item/ammo_casing/p50/marksman
+ name = ".50 marksman round bullet casing"
+ desc = "A .50 caliber marksman round casing."
+ projectile_type = /obj/projectile/bullet/p50/marksman
diff --git a/code/modules/projectiles/boxes_magazines/external/sniper.dm b/code/modules/projectiles/boxes_magazines/external/sniper.dm
index ee249d70989..0cf429c1e00 100644
--- a/code/modules/projectiles/boxes_magazines/external/sniper.dm
+++ b/code/modules/projectiles/boxes_magazines/external/sniper.dm
@@ -23,3 +23,9 @@
desc = "An extremely powerful round capable of passing straight through cover and anyone unfortunate enough to be behind it."
ammo_type = /obj/item/ammo_casing/p50/penetrator
max_ammo = 5
+
+/obj/item/ammo_box/magazine/sniper_rounds/marksman
+ name = "sniper rounds (marksman)"
+ desc = "An extremely fast sniper round able to pretty much instantly shoot through something."
+ ammo_type = /obj/item/ammo_casing/p50/marksman
+ max_ammo = 5
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 83210d8fbb1..e3c7e979717 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -71,12 +71,6 @@
var/flight_x_offset = 0
var/flight_y_offset = 0
- //Zooming
- var/zoomable = FALSE //whether the gun generates a Zoom action on creation
- var/zoomed = FALSE //Zoom toggle
- var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect)
- var/zoom_out_amt = 0
- var/datum/action/toggle_scope_zoom/azoom
var/pb_knockback = 0
/obj/item/gun/Initialize(mapload)
@@ -85,7 +79,6 @@
pin = new pin(src)
if(gun_light)
alight = new(src)
- build_zooming()
/obj/item/gun/Destroy()
if(isobj(pin)) //Can still be the initial path, then we skip
@@ -96,8 +89,6 @@
QDEL_NULL(bayonet)
if(chambered) //Not all guns are chambered (EMP'ed energy guns etc)
QDEL_NULL(chambered)
- if(azoom)
- QDEL_NULL(azoom)
if(isatom(suppressed)) //SUPPRESSED IS USED AS BOTH A TRUE/FALSE AND AS A REF, WHAT THE FUCKKKKKKKKKKKKKKKKK
QDEL_NULL(suppressed)
return ..()
@@ -146,11 +137,6 @@
if(can_bayonet)
. += "It has a bayonet lug on it."
-/obj/item/gun/equipped(mob/living/user, slot)
- . = ..()
- if(zoomed && user.get_active_held_item() != src)
- zoom(user, user.dir, FALSE) //we can only stay zoomed in if it's in our hands //yeah and we only unzoom if we're actually zoomed using the gun!!
-
//called after the gun has successfully fired its chambered ammo.
/obj/item/gun/proc/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
handle_chamber(empty_chamber, from_firing, chamber_next_round)
@@ -225,6 +211,8 @@
return
if(firing_burst)
return
+ if(SEND_SIGNAL(src, COMSIG_GUN_TRY_FIRE, user, target, flag, params) & COMPONENT_CANCEL_GUN_FIRE)
+ return
if(flag) //It's adjacent, is the user, or is on the user's person
if(target in user.contents) //can't shoot stuff inside us.
return
@@ -603,18 +591,6 @@
update_appearance()
update_action_buttons()
-/obj/item/gun/pickup(mob/user)
- ..()
- if(azoom)
- azoom.Grant(user)
-
-/obj/item/gun/dropped(mob/user)
- . = ..()
- if(azoom)
- azoom.Remove(user)
- if(zoomed)
- zoom(user, user.dir, FALSE)
-
/obj/item/gun/update_overlays()
. = ..()
if(gun_light)
@@ -687,66 +663,5 @@
/obj/item/gun/proc/before_firing(atom/target,mob/user)
return
-/////////////
-// ZOOMING //
-/////////////
-
-/datum/action/toggle_scope_zoom
- name = "Toggle Scope"
- check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_LYING
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "sniper_zoom"
- var/obj/item/gun/gun = null
-
-/datum/action/toggle_scope_zoom/Trigger(trigger_flags)
- . = ..()
- if(!.)
- return
- gun.zoom(owner, owner.dir)
-
-/datum/action/toggle_scope_zoom/IsAvailable()
- . = ..()
- if(owner.get_active_held_item() != gun)
- . = FALSE
- if(!. && gun)
- gun.zoom(owner, owner.dir, FALSE)
-
-/datum/action/toggle_scope_zoom/Remove(mob/living/L)
- gun.zoom(L, L.dir, FALSE)
- ..()
-
-/obj/item/gun/proc/rotate(atom/thing, old_dir, new_dir)
- SIGNAL_HANDLER
-
- if(ismob(thing))
- var/mob/lad = thing
- lad.client.view_size.zoomOut(zoom_out_amt, zoom_amt, new_dir)
-
-/obj/item/gun/proc/zoom(mob/living/user, direc, forced_zoom)
- if(!user || !user.client)
- return
-
- if(isnull(forced_zoom))
- zoomed = !zoomed
- else
- zoomed = forced_zoom
-
- if(zoomed)
- RegisterSignal(user, COMSIG_ATOM_DIR_CHANGE, .proc/rotate)
- user.client.view_size.zoomOut(zoom_out_amt, zoom_amt, direc)
- else
- UnregisterSignal(user, COMSIG_ATOM_DIR_CHANGE)
- user.client.view_size.zoomIn()
- return zoomed
-
-//Proc, so that gun accessories/scopes/etc. can easily add zooming.
-/obj/item/gun/proc/build_zooming()
- if(azoom)
- return
-
- if(zoomable)
- azoom = new()
- azoom.gun = src
-
#undef FIRING_PIN_REMOVAL_DELAY
#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index e02c2bfddbf..f11bc640c47 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -350,15 +350,16 @@
fire_delay = 40
burst_size = 1
w_class = WEIGHT_CLASS_NORMAL
- zoomable = TRUE
- zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you.
- zoom_out_amt = 5
slot_flags = ITEM_SLOT_BACK
actions_types = list()
mag_display = TRUE
suppressor_x_offset = 3
suppressor_y_offset = 3
+/obj/item/gun/ballistic/automatic/sniper_rifle/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/scope, range_modifier = 2)
+
/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate
name = "syndicate sniper rifle"
desc = "An illegally modified .50 cal sniper rifle with suppression compatibility. Quickscoping still doesn't work."
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 486e3b283a7..de6a269e464 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -94,6 +94,8 @@
var/ricochet_auto_aim_angle = 30
/// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle
var/ricochet_incidence_leeway = 40
+ /// Can our ricochet autoaim hit our firer?
+ var/ricochet_shoots_firer = TRUE
///If the object being hit can pass ths damage on to something else, it should not do it for this bullet
var/force_hit = FALSE
@@ -325,7 +327,7 @@
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
best_angle += NICE_SHOT_RICOCHET_BONUS
for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc))
- if(L.stat == DEAD || !is_in_sight(src, L))
+ if(L.stat == DEAD || !is_in_sight(src, L) || (!ricochet_shoots_firer && L == firer))
continue
var/our_angle = abs(closer_angle_difference(Angle, get_angle(src.loc, L.loc)))
if(our_angle < best_angle)
diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm
index c9403b65e16..db89bd72031 100644
--- a/code/modules/projectiles/projectile/bullets/sniper.dm
+++ b/code/modules/projectiles/projectile/bullets/sniper.dm
@@ -47,3 +47,29 @@
damage = 25
speed = 0.3
range = 16
+
+/obj/projectile/bullet/p50/marksman
+ name = ".50 marksman round"
+ damage = 50
+ paralyze = 0
+ tracer_type = /obj/effect/projectile/tracer/sniper
+ impact_type = /obj/effect/projectile/impact/sniper
+ muzzle_type = /obj/effect/projectile/muzzle/sniper
+ hitscan = TRUE
+ impact_effect_type = null
+ hitscan_light_intensity = 3
+ hitscan_light_range = 0.75
+ hitscan_light_color_override = LIGHT_COLOR_YELLOW
+ muzzle_flash_intensity = 5
+ muzzle_flash_range = 1
+ muzzle_flash_color_override = LIGHT_COLOR_YELLOW
+ impact_light_intensity = 5
+ impact_light_range = 1
+ impact_light_color_override = LIGHT_COLOR_YELLOW
+ ricochets_max = 1
+ ricochet_chance = 100
+ ricochet_auto_aim_angle = 45
+ ricochet_auto_aim_range = 15
+ ricochet_incidence_leeway = 90
+ ricochet_decay_damage = 1
+ ricochet_shoots_firer = FALSE
diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm
index 7ec57b8baad..c6934a95eeb 100644
--- a/code/modules/uplink/uplink_items/nukeops.dm
+++ b/code/modules/uplink/uplink_items/nukeops.dm
@@ -331,6 +331,12 @@
item = /obj/item/ammo_box/magazine/sniper_rounds/soporific
cost = 6
+/datum/uplink_item/ammo/sniper/marksman
+ name = ".50 Marksman Magazine"
+ desc = "A 5-round magazine of marksman ammo designed for use with .50 sniper rifles. Blast your enemies with instant shots!"
+ item = /obj/item/ammo_box/magazine/sniper_rounds/marksman
+ cost = 5
+
/datum/uplink_item/ammo/carbine
name = "5.56mm Toploader Magazine"
desc = "An additional 30-round 5.56mm magazine; suitable for use with the M-90gl carbine. \
diff --git a/icons/effects/mouse_pointers/scope_hide.dmi b/icons/effects/mouse_pointers/scope_hide.dmi
new file mode 100644
index 00000000000..a6182e8fc5f
Binary files /dev/null and b/icons/effects/mouse_pointers/scope_hide.dmi differ
diff --git a/icons/hud/screen_full.dmi b/icons/hud/screen_full.dmi
index f54a0bd9444..1e2a42b98e0 100644
Binary files a/icons/hud/screen_full.dmi and b/icons/hud/screen_full.dmi differ
diff --git a/icons/obj/guns/projectiles_impact.dmi b/icons/obj/guns/projectiles_impact.dmi
index fc005bd00d2..ae5e052287c 100644
Binary files a/icons/obj/guns/projectiles_impact.dmi and b/icons/obj/guns/projectiles_impact.dmi differ
diff --git a/icons/obj/guns/projectiles_muzzle.dmi b/icons/obj/guns/projectiles_muzzle.dmi
index 2ef59b8d2d3..9bc3869d8c9 100644
Binary files a/icons/obj/guns/projectiles_muzzle.dmi and b/icons/obj/guns/projectiles_muzzle.dmi differ
diff --git a/icons/obj/guns/projectiles_tracer.dmi b/icons/obj/guns/projectiles_tracer.dmi
index 8efd099a095..b4edd672ac9 100644
Binary files a/icons/obj/guns/projectiles_tracer.dmi and b/icons/obj/guns/projectiles_tracer.dmi differ
diff --git a/sound/weapons/scope.ogg b/sound/weapons/scope.ogg
new file mode 100644
index 00000000000..37d70b6ff4c
Binary files /dev/null and b/sound/weapons/scope.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 8802f945ff9..ed5a726e728 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -762,6 +762,7 @@
#include "code\datums\components\rename.dm"
#include "code\datums\components\rot.dm"
#include "code\datums\components\rotation.dm"
+#include "code\datums\components\scope.dm"
#include "code\datums\components\shell.dm"
#include "code\datums\components\shielded.dm"
#include "code\datums\components\shrink.dm"