Adds a scope component, removes old zooming and adds sniper marksman ammo. (#66218)

Removes the old sniper rifle zoom, replaces it with a scope component. the scope activates on right click and lets your camera follow your mouse.
https://streamable.com/2c63u4 (due to byond rounding some shots were weirdly missed in that video, its fixed now)
Also adds sniper marksman ammo to the nukie uplink. It does slightly less damage, but it is hitscan and has one guaranteed ricochet shot, so you can shoot a wall and it could still hit someone.
This commit is contained in:
Fikou
2022-04-25 15:03:49 -07:00
committed by GitHub
parent a156d7e3d0
commit 742b272f52
20 changed files with 247 additions and 94 deletions
@@ -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)
+178
View File
@@ -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 <b>right-click</b>.")
/**
* 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)
@@ -44,3 +44,6 @@
/obj/effect/projectile/impact/solar
name = "solar impact"
icon_state = "impact_solar"
/obj/effect/projectile/impact/sniper
icon_state = "sniper"
@@ -35,3 +35,6 @@
/obj/effect/projectile/muzzle/solar
icon_state = "muzzle_solar"
/obj/effect/projectile/muzzle/sniper
icon_state = "sniper"
@@ -73,3 +73,6 @@
/obj/effect/projectile/tracer/laser/emitter
name = "emitter beam"
icon_state = "emitter"
/obj/effect/projectile/tracer/sniper
icon_state = "sniper"
@@ -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"
@@ -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
@@ -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
+2 -87
View File
@@ -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 <b>bayonet</b> 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
@@ -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."
+3 -1
View File
@@ -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)
@@ -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
@@ -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. \
Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.
+1
View File
@@ -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"