diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 66ecf9ea034f..8d6fb4266776 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -294,6 +294,8 @@
if(sorted && length(candidates))
candidates = sort_list(candidates, GLOBAL_PROC_REF(cmp_obs_larvaqueuetime_asc))
+ GLOB.xeno_queue_candidate_count = length(candidates)
+
return candidates
/**
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 6c689e995504..646b8ec2c854 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -27,3 +27,6 @@ GLOBAL_VAR_INIT(time_offset, setup_offset())
/// Sets the offset 2 lines above.
/proc/setup_offset()
return rand(10 MINUTES, 24 HOURS)
+
+/// The last count of possible candidates in the xeno larva queue (updated via get_alien_candidates)
+GLOBAL_VAR(xeno_queue_candidate_count)
diff --git a/code/game/gamemodes/cm_initialize.dm b/code/game/gamemodes/cm_initialize.dm
index 578105f98728..cc1e5449ca7a 100644
--- a/code/game/gamemodes/cm_initialize.dm
+++ b/code/game/gamemodes/cm_initialize.dm
@@ -356,17 +356,21 @@ Additional game mode variables.
else
available_xenos_non_ssd += cur_xeno
- var/datum/hive_status/hive
- for(var/hivenumber in GLOB.hive_datum)
- hive = GLOB.hive_datum[hivenumber]
- if(!hive.hardcore && hive.stored_larva && (hive.hive_location || (world.time < XENO_BURIED_LARVA_TIME_LIMIT + SSticker.round_start_time)))
- if(SSticker.mode && (SSticker.mode.flags_round_type & MODE_RANDOM_HIVE))
- available_xenos |= "any buried larva"
- LAZYADD(available_xenos["any buried larva"], hive)
- else
- var/larva_option = "buried larva ([hive])"
- available_xenos += larva_option
- available_xenos[larva_option] = list(hive)
+ // Only offer buried larva if there is no queue:
+ // This basically means this block of code will almost never execute, because we are instead relying on the hive cores/larva pops to handle their larva
+ // Technically this should be after a get_alien_candidates() call to be accurate, but we are intentionally trying to not call that proc as much as possible
+ if(GLOB.xeno_queue_candidate_count < 1)
+ var/datum/hive_status/hive
+ for(var/hivenumber in GLOB.hive_datum)
+ hive = GLOB.hive_datum[hivenumber]
+ if(!hive.hardcore && hive.stored_larva && (hive.hive_location || (world.time < XENO_BURIED_LARVA_TIME_LIMIT + SSticker.round_start_time)))
+ if(SSticker.mode && (SSticker.mode.flags_round_type & MODE_RANDOM_HIVE))
+ available_xenos |= "any buried larva"
+ LAZYADD(available_xenos["any buried larva"], hive)
+ else
+ var/larva_option = "buried larva ([hive])"
+ available_xenos += larva_option
+ available_xenos[larva_option] = list(hive)
if(!available_xenos.len || (instant_join && !available_xenos_non_ssd.len))
if(!xeno_candidate.client || !xeno_candidate.client.prefs || !(xeno_candidate.client.prefs.be_special & BE_ALIEN_AFTER_DEATH))
@@ -446,7 +450,7 @@ Additional game mode variables.
to_chat(xeno_candidate, SPAN_WARNING("You cannot join if the xenomorph is dead."))
return FALSE
- if(new_xeno.stat == UNCONSCIOUS)
+ if(new_xeno.health <= 0)
to_chat(xeno_candidate, SPAN_WARNING("You cannot join if the xenomorph is in critical condition or unconscious."))
return FALSE
diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm
index 9c122452d41f..4eff0240939d 100644
--- a/code/modules/cm_aliens/structures/special/pylon_core.dm
+++ b/code/modules/cm_aliens/structures/special/pylon_core.dm
@@ -263,25 +263,26 @@
linked_hive.hive_ui.update_burrowed_larva()
qdel(worm)
+ var/count_spawned = 0
var/spawning_larva = can_spawn_larva() && (last_larva_time + spawn_cooldown) < world.time
if(spawning_larva)
last_larva_time = world.time
if(spawning_larva || (last_larva_queue_time + spawn_cooldown * 4) < world.time)
last_larva_queue_time = world.time
var/list/players_with_xeno_pref = get_alien_candidates(linked_hive)
- if(players_with_xeno_pref && players_with_xeno_pref.len)
+ if(length(players_with_xeno_pref))
if(spawning_larva && spawn_burrowed_larva(players_with_xeno_pref[1]))
// We were in spawning_larva mode and successfully spawned someone
- message_alien_candidates(players_with_xeno_pref, dequeued = 1)
- else
- // Just time to update everyone their queue status (or the spawn failed)
- message_alien_candidates(players_with_xeno_pref, dequeued = 0)
+ count_spawned = 1
+ // Update everyone's queue status
+ message_alien_candidates(players_with_xeno_pref, dequeued = count_spawned)
if(linked_hive.hijack_burrowed_surge && (last_surge_time + surge_cooldown) < world.time)
last_surge_time = world.time
linked_hive.stored_larva++
linked_hive.hijack_burrowed_left--
- notify_ghosts(header = "Claim Xeno", message = "The Hive has gained another burrowed larva! Click to take it.", source = src, action = NOTIFY_JOIN_XENO, enter_link = "join_xeno")
+ if(GLOB.xeno_queue_candidate_count < 1 + count_spawned)
+ notify_ghosts(header = "Claim Xeno", message = "The Hive has gained another burrowed larva! Click to take it.", source = src, action = NOTIFY_JOIN_XENO, enter_link = "join_xeno")
if(surge_cooldown > 30 SECONDS) //mostly for sanity purposes
surge_cooldown = surge_cooldown - surge_incremental_reduction //ramps up over time
if(linked_hive.hijack_burrowed_left < 1)
diff --git a/code/modules/projectiles/guns/rifles.dm b/code/modules/projectiles/guns/rifles.dm
index 4723ad882368..65e4a6f2b7b3 100644
--- a/code/modules/projectiles/guns/rifles.dm
+++ b/code/modules/projectiles/guns/rifles.dm
@@ -435,6 +435,7 @@
accepted_ammo = list(
/obj/item/ammo_magazine/rifle,
+ /obj/item/ammo_magazine/rifle/rubber,
/obj/item/ammo_magazine/rifle/extended,
/obj/item/ammo_magazine/rifle/ap,
/obj/item/ammo_magazine/rifle/incendiary,
diff --git a/code/modules/projectiles/guns/specialist.dm b/code/modules/projectiles/guns/specialist.dm
deleted file mode 100644
index 71afd0bdd09a..000000000000
--- a/code/modules/projectiles/guns/specialist.dm
+++ /dev/null
@@ -1,1370 +0,0 @@
-//-------------------------------------------------------
-//SNIPER RIFLES
-//Keyword rifles. They are subtype of rifles, but still contained here as a specialist weapon.
-
-//Because this parent type did not exist
-//Note that this means that snipers will have a slowdown of 3, due to the scope
-/obj/item/weapon/gun/rifle/sniper
- aim_slowdown = SLOWDOWN_ADS_SPECIALIST
- wield_delay = WIELD_DELAY_SLOW
-
- var/has_aimed_shot = TRUE
- var/aiming_time = 1.25 SECONDS
- var/aimed_shot_cooldown
- var/aimed_shot_cooldown_delay = 2.5 SECONDS
-
- var/enable_aimed_shot_laser = TRUE
- var/sniper_lockon_icon = "sniper_lockon"
- var/obj/effect/ebeam/sniper_beam_type = /obj/effect/ebeam/laser
- var/sniper_beam_icon = "laser_beam"
- var/skill_locked = TRUE
-
-/obj/item/weapon/gun/rifle/sniper/get_examine_text(mob/user)
- . = ..()
- if(!has_aimed_shot)
- return
- . += SPAN_NOTICE("This weapon has an unique ability, Aimed Shot, allowing it to deal great damage after a windup.
Additionally, the aimed shot can be sped up with a tracking laser, which is enabled by default but may be disabled.")
-
-/obj/item/weapon/gun/rifle/sniper/Initialize(mapload, spawn_empty)
- if(has_aimed_shot)
- LAZYADD(actions_types, list(/datum/action/item_action/specialist/aimed_shot, /datum/action/item_action/specialist/toggle_laser))
- return ..()
-
-/obj/item/weapon/gun/rifle/sniper/able_to_fire(mob/living/user)
- . = ..()
- if(. && istype(user) && skill_locked) //Let's check all that other stuff first.
- if(!skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_SNIPER)
- to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
- return 0
-
-// Aimed shot ability
-/datum/action/item_action/specialist/aimed_shot
- ability_primacy = SPEC_PRIMARY_ACTION_2
- var/minimum_aim_distance = 2
-
-/datum/action/item_action/specialist/aimed_shot/New(mob/living/user, obj/item/holder)
- ..()
- name = "Aimed Shot"
- button.name = name
- button.overlays.Cut()
- var/image/IMG = image('icons/mob/hud/actions.dmi', button, "sniper_aim")
- button.overlays += IMG
- var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
- sniper_rifle.aimed_shot_cooldown = world.time
-
-
-/datum/action/item_action/specialist/aimed_shot/action_activate()
- if(!ishuman(owner))
- return
- var/mob/living/carbon/human/H = owner
- if(H.selected_ability == src)
- to_chat(H, "You will no longer use [name] with \
- [H.client && H.client.prefs && H.client.prefs.toggle_prefs & TOGGLE_MIDDLE_MOUSE_CLICK ? "middle-click" : "shift-click"].")
- button.icon_state = "template"
- H.selected_ability = null
- else
- to_chat(H, "You will now use [name] with \
- [H.client && H.client.prefs && H.client.prefs.toggle_prefs & TOGGLE_MIDDLE_MOUSE_CLICK ? "middle-click" : "shift-click"].")
- if(H.selected_ability)
- H.selected_ability.button.icon_state = "template"
- H.selected_ability = null
- button.icon_state = "template_on"
- H.selected_ability = src
-
-/datum/action/item_action/specialist/aimed_shot/can_use_action()
- var/mob/living/carbon/human/H = owner
- if(istype(H) && !H.is_mob_incapacitated() && !H.lying && (holder_item == H.r_hand || holder_item || H.l_hand))
- return TRUE
-
-/datum/action/item_action/specialist/aimed_shot/proc/use_ability(atom/A)
- var/mob/living/carbon/human/human = owner
- if(!istype(A, /mob/living))
- return
-
- var/mob/living/target = A
-
- if(target.stat == DEAD || target == human)
- return
-
- var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
- if(world.time < sniper_rifle.aimed_shot_cooldown)
- return
-
- if(!check_can_use(target))
- return
-
- human.face_atom(target)
-
- ///Add a decisecond to the default 1.5 seconds for each two tiles to hit.
- var/distance = round(get_dist(target, human) * 0.5)
- var/f_aiming_time = sniper_rifle.aiming_time + distance
-
- var/aim_multiplier = 1
- var/aiming_buffs
-
- if(sniper_rifle.enable_aimed_shot_laser)
- aim_multiplier = 0.6
- aiming_buffs++
-
- if(HAS_TRAIT(target, TRAIT_SPOTTER_LAZED))
- aim_multiplier = 0.5
- aiming_buffs++
-
- if(aiming_buffs > 1)
- aim_multiplier = 0.35
-
- f_aiming_time *= aim_multiplier
-
- var/image/lockon_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = sniper_rifle.sniper_lockon_icon)
-
- var/x_offset = -target.pixel_x + target.base_pixel_x
- var/y_offset = (target.icon_size - world.icon_size) * 0.5 - target.pixel_y + target.base_pixel_y
-
- lockon_icon.pixel_x = x_offset
- lockon_icon.pixel_y = y_offset
- target.overlays += lockon_icon
-
- var/image/lockon_direction_icon
- if(!sniper_rifle.enable_aimed_shot_laser)
- lockon_direction_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "[sniper_rifle.sniper_lockon_icon]_direction", dir = get_cardinal_dir(target, human))
- lockon_direction_icon.pixel_x = x_offset
- lockon_direction_icon.pixel_y = y_offset
- target.overlays += lockon_direction_icon
- if(human.client)
- playsound_client(human.client, 'sound/weapons/TargetOn.ogg', human, 50)
- playsound(target, 'sound/weapons/TargetOn.ogg', 70, FALSE, 8, falloff = 0.4)
-
- var/datum/beam/laser_beam
- if(sniper_rifle.enable_aimed_shot_laser)
- laser_beam = target.beam(human, sniper_rifle.sniper_beam_icon, 'icons/effects/beam.dmi', (f_aiming_time + 1 SECONDS), beam_type = sniper_rifle.sniper_beam_type)
- laser_beam.visuals.alpha = 0
- animate(laser_beam.visuals, alpha = initial(laser_beam.visuals.alpha), f_aiming_time, easing = SINE_EASING|EASE_OUT)
-
- ////timer is (f_spotting_time + 1 SECONDS) because sometimes it janks out before the doafter is done. blame sleeps or something
-
- if(!do_after(human, f_aiming_time, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, NO_BUSY_ICON))
- target.overlays -= lockon_icon
- target.overlays -= lockon_direction_icon
- qdel(laser_beam)
- return
-
- target.overlays -= lockon_icon
- target.overlays -= lockon_direction_icon
- qdel(laser_beam)
-
- if(!check_can_use(target, TRUE))
- return
-
- var/obj/projectile/aimed_proj = sniper_rifle.in_chamber
- aimed_proj.projectile_flags |= PROJECTILE_BULLSEYE
- aimed_proj.AddComponent(/datum/component/homing_projectile, target, human)
- sniper_rifle.Fire(target, human)
-
-/datum/action/item_action/specialist/aimed_shot/proc/check_can_use(mob/M, cover_lose_focus)
- var/mob/living/carbon/human/H = owner
- var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
-
- if(!can_use_action())
- return FALSE
-
- if(sniper_rifle != H.r_hand && sniper_rifle != H.l_hand)
- to_chat(H, SPAN_WARNING("How do you expect to do this without your sniper rifle?"))
- return FALSE
-
- if(!(sniper_rifle.flags_item & WIELDED))
- to_chat(H, SPAN_WARNING("Your aim is not stable enough with one hand. Use both hands!"))
- return FALSE
-
- if(!sniper_rifle.in_chamber)
- to_chat(H, SPAN_WARNING("\The [sniper_rifle] is unloaded!"))
- return FALSE
-
- if(get_dist(H, M) < minimum_aim_distance)
- to_chat(H, SPAN_WARNING("\The [M] is too close to get a proper shot!"))
- return FALSE
-
- var/obj/projectile/P = sniper_rifle.in_chamber
- // TODO: Make the below logic only occur in certain circumstances. Check goggles, maybe? -Kaga
- if(check_shot_is_blocked(H, M, P))
- to_chat(H, SPAN_WARNING("Something is in the way, or you're out of range!"))
- if(cover_lose_focus)
- to_chat(H, SPAN_WARNING("You lose focus."))
- COOLDOWN_START(sniper_rifle, aimed_shot_cooldown, sniper_rifle.aimed_shot_cooldown_delay * 0.5)
- return FALSE
-
- COOLDOWN_START(sniper_rifle, aimed_shot_cooldown, sniper_rifle.aimed_shot_cooldown_delay)
- return TRUE
-
-/datum/action/item_action/specialist/aimed_shot/proc/check_shot_is_blocked(mob/firer, mob/target, obj/projectile/P)
- var/list/turf/path = getline2(firer, target, include_from_atom = FALSE)
- if(!path.len || get_dist(firer, target) > P.ammo.max_range)
- return TRUE
-
- var/blocked = FALSE
- for(var/turf/T in path)
- if(T.density || T.opacity)
- blocked = TRUE
- break
-
- for(var/obj/O in T)
- if(O.get_projectile_hit_boolean(P))
- blocked = TRUE
- break
-
- for(var/obj/effect/particle_effect/smoke/S in T)
- blocked = TRUE
- break
-
- return blocked
-
-// Snipers may enable or disable their laser tracker at will.
-/datum/action/item_action/specialist/toggle_laser
-
-/datum/action/item_action/specialist/toggle_laser/New(mob/living/user, obj/item/holder)
- ..()
- name = "Toggle Tracker Laser"
- button.name = name
- button.overlays.Cut()
- var/image/IMG = image('icons/mob/hud/actions.dmi', button, "sniper_toggle_laser_on")
- button.overlays += IMG
- update_button_icon()
-
-/datum/action/item_action/specialist/toggle_laser/update_button_icon()
- var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
-
- var/icon = 'icons/mob/hud/actions.dmi'
- var/icon_state = "sniper_toggle_laser_[sniper_rifle.enable_aimed_shot_laser ? "on" : "off"]"
-
- button.overlays.Cut()
- var/image/IMG = image(icon, button, icon_state)
- button.overlays += IMG
-
-/datum/action/item_action/specialist/toggle_laser/can_use_action()
- var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
-
- if(owner.is_mob_incapacitated())
- return FALSE
-
- if(owner.get_held_item() != sniper_rifle)
- to_chat(owner, SPAN_WARNING("How do you expect to do this without the sniper rifle in your hand?"))
- return FALSE
- return TRUE
-
-/datum/action/item_action/specialist/toggle_laser/action_activate()
- var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
-
- if(owner.get_held_item() != sniper_rifle)
- to_chat(owner, SPAN_WARNING("How do you expect to do this without the sniper rifle in your hand?"))
- return FALSE
- sniper_rifle.toggle_laser(owner, src)
-
-/obj/item/weapon/gun/rifle/sniper/proc/toggle_laser(mob/user, datum/action/toggling_action)
- enable_aimed_shot_laser = !enable_aimed_shot_laser
- to_chat(user, SPAN_NOTICE("You flip a switch on \the [src] and [enable_aimed_shot_laser ? "enable" : "disable"] its targeting laser."))
- playsound(user, 'sound/machines/click.ogg', 15, TRUE)
- if(!toggling_action)
- toggling_action = locate(/datum/action/item_action/specialist/toggle_laser) in actions
- if(toggling_action)
- toggling_action.update_button_icon()
-
-/obj/item/weapon/gun/rifle/sniper/verb/toggle_gun_laser()
- set category = "Weapons"
- set name = "Toggle Laser"
- set desc = "Toggles your laser on or off."
- set src = usr.contents
-
- var/obj/item/weapon/gun/rifle/sniper/sniper = get_active_firearm(usr)
- if((sniper == src) && has_aimed_shot)
- toggle_laser(usr)
-
-//Pow! Headshot.
-/obj/item/weapon/gun/rifle/sniper/M42A
- name = "\improper M42A scoped rifle"
- desc = "A heavy sniper rifle manufactured by Armat Systems. It has a scope system and fires armor penetrating rounds out of a 15-round magazine.\n'Peace Through Superior Firepower'"
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "m42a"
- item_state = "m42a"
- unacidable = TRUE
- indestructible = 1
-
- fire_sound = 'sound/weapons/gun_sniper.ogg'
- current_mag = /obj/item/ammo_magazine/sniper
- force = 12
- wield_delay = WIELD_DELAY_HORRIBLE //Ends up being 1.6 seconds due to scope
- zoomdevicename = "scope"
- attachable_allowed = list(/obj/item/attachable/bipod)
- starting_attachment_types = list(/obj/item/attachable/sniperbarrel)
- flags_gun_features = GUN_AUTO_EJECTOR|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER
- map_specific_decoration = TRUE
-
- flags_item = TWOHANDED|NO_CRYO_STORE
-
-/obj/item/weapon/gun/rifle/sniper/M42A/verb/toggle_scope_zoom_level()
- set name = "Toggle Scope Zoom Level"
- set category = "Weapons"
- set src in usr
- var/obj/item/attachable/scope/variable_zoom/S = attachments["rail"]
- S.toggle_zoom_level()
-
-/obj/item/weapon/gun/rifle/sniper/M42A/handle_starting_attachment()
- ..()
- var/obj/item/attachable/scope/variable_zoom/S = new(src)
- S.hidden = TRUE
- S.flags_attach_features &= ~ATTACH_REMOVABLE
- S.Attach(src)
- update_attachable(S.slot)
-
-/obj/item/weapon/gun/rifle/sniper/M42A/set_bullet_traits()
- LAZYADD(traits_to_give, list(
- BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
- ))
-
-/obj/item/weapon/gun/rifle/sniper/M42A/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 39, "muzzle_y" = 17,"rail_x" = 12, "rail_y" = 20, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
-
-
-/obj/item/weapon/gun/rifle/sniper/M42A/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_7*3)
- set_burst_amount(BURST_AMOUNT_TIER_1)
- accuracy_mult = BASE_ACCURACY_MULT * 3 //you HAVE to be able to hit
- scatter = SCATTER_AMOUNT_TIER_8
- damage_mult = BASE_BULLET_DAMAGE_MULT
- recoil = RECOIL_AMOUNT_TIER_5
-
-/obj/item/weapon/gun/rifle/sniper/xm43e1
- name = "\improper XM43E1 experimental anti-materiel rifle"
- desc = "An experimental anti-materiel rifle produced by Armat Systems, recently reacquired from the deep storage of an abandoned prototyping facility. This one in particular is currently undergoing field testing. Chambered in 10x99mm Caseless."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "xm42b"
- item_state = "xm42b"
- unacidable = TRUE
- indestructible = 1
-
- fire_sound = 'sound/weapons/sniper_heavy.ogg'
- current_mag = /obj/item/ammo_magazine/sniper/anti_materiel //Renamed from anti-tank to align with new identity/description. Other references have been changed as well. -Kaga
- force = 12
- wield_delay = WIELD_DELAY_HORRIBLE //Ends up being 1.6 seconds due to scope
- zoomdevicename = "scope"
- attachable_allowed = list(/obj/item/attachable/bipod)
- flags_gun_features = GUN_AUTO_EJECTOR|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER
- starting_attachment_types = list(/obj/item/attachable/sniperbarrel)
- sniper_beam_type = /obj/effect/ebeam/laser/intense
- sniper_beam_icon = "laser_beam_intense"
- sniper_lockon_icon = "sniper_lockon_intense"
-
-/obj/item/weapon/gun/rifle/sniper/XM42B/handle_starting_attachment()
- ..()
- var/obj/item/attachable/scope/variable_zoom/S = new(src)
- S.icon_state = "pmcscope"
- S.attach_icon = "pmcscope"
- S.flags_attach_features &= ~ATTACH_REMOVABLE
- S.Attach(src)
- update_attachable(S.slot)
-
-
-/obj/item/weapon/gun/rifle/sniper/XM42B/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 18,"rail_x" = 15, "rail_y" = 19, "under_x" = 20, "under_y" = 15, "stock_x" = 20, "stock_y" = 15)
-
-
-/obj/item/weapon/gun/rifle/sniper/XM42B/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_6 * 6 )//Big boy damage, but it takes a lot of time to fire a shot.
- //Kaga: Adjusted from 56 (Tier 4, 7*8) -> 30 (Tier 6, 5*6) ticks. 95 really wasn't big-boy damage anymore, although I updated it to 125 to remain consistent with the other 10x99mm caliber weapon (M42C). Now takes only twice as long as the M42A.
- set_burst_amount(BURST_AMOUNT_TIER_1)
- accuracy_mult = BASE_ACCURACY_MULT + 2*HIT_ACCURACY_MULT_TIER_10 //Who coded this like this, and why? It just calculates out to 1+1=2. Leaving a note here to check back later.
- scatter = SCATTER_AMOUNT_TIER_10
- damage_mult = BASE_BULLET_DAMAGE_MULT
- recoil = RECOIL_AMOUNT_TIER_1
-
-/obj/item/weapon/gun/rifle/sniper/XM42B/set_bullet_traits()
- LAZYADD(traits_to_give, list(
- BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff),
- BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_penetrating),
- BULLET_TRAIT_ENTRY_ID("turfs", /datum/element/bullet_trait_damage_boost, 11, GLOB.damage_boost_turfs),
- BULLET_TRAIT_ENTRY_ID("breaching", /datum/element/bullet_trait_damage_boost, 11, GLOB.damage_boost_breaching),
- //At 1375 per shot it'll take 1 shot to break resin turfs, and a full mag of 8 to break reinforced walls.
- BULLET_TRAIT_ENTRY_ID("pylons", /datum/element/bullet_trait_damage_boost, 6, GLOB.damage_boost_pylons)
- //At 750 per shot it'll take 3 to break a Pylon (1800 HP). No Damage Boost vs other xeno structures yet, those will require a whole new list w/ the damage_boost trait.
- ))
-
-/*
-//Disabled until an identity is better defined. -Kaga
-/obj/item/weapon/gun/rifle/sniper/M42B/afterattack(atom/target, mob/user, flag)
- if(able_to_fire(user))
- if(get_dist(target,user) <= 8)
- to_chat(user, SPAN_WARNING("The [src.name] beeps, indicating that the target is within an unsafe proximity to the rifle, refusing to fire."))
- return
- else ..()
-*/
-
-/obj/item/weapon/gun/rifle/sniper/elite
- name = "\improper M42C anti-tank sniper rifle"
- desc = "A high-end superheavy magrail sniper rifle from Weyland-Armat chambered in a specialized variant of the heaviest ammo available, 10x99mm Caseless. This weapon requires a specialized armor rig for recoil mitigation in order to be used effectively."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/wy.dmi'
- icon_state = "m42c"
- item_state = "m42c" //NEEDS A TWOHANDED STATE
-
- fire_sound = 'sound/weapons/sniper_heavy.ogg'
- current_mag = /obj/item/ammo_magazine/sniper/elite
- force = 17
- zoomdevicename = "scope"
- flags_gun_features = GUN_AUTO_EJECTOR|GUN_WY_RESTRICTED|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER
- starting_attachment_types = list(/obj/item/attachable/sniperbarrel)
- sniper_beam_type = /obj/effect/ebeam/laser/intense
- sniper_beam_icon = "laser_beam_intense"
- sniper_lockon_icon = "sniper_lockon_intense"
-
-/obj/item/weapon/gun/rifle/sniper/elite/handle_starting_attachment()
- ..()
- var/obj/item/attachable/scope/S = new(src)
- S.icon_state = "pmcscope"
- S.attach_icon = "pmcscope"
- S.flags_attach_features &= ~ATTACH_REMOVABLE
- S.Attach(src)
- update_attachable(S.slot)
-
-/obj/item/weapon/gun/rifle/sniper/elite/set_bullet_traits()
- LAZYADD(traits_to_give, list(
- BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
- ))
-
-/obj/item/weapon/gun/rifle/sniper/elite/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 18,"rail_x" = 15, "rail_y" = 19, "under_x" = 20, "under_y" = 15, "stock_x" = 20, "stock_y" = 15)
-
-/obj/item/weapon/gun/rifle/sniper/elite/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_6*5)
- set_burst_amount(BURST_AMOUNT_TIER_1)
- accuracy_mult = BASE_ACCURACY_MULT * 3 //Was previously BAM + HAMT10, similar to the XM42B, and coming out to 1.5? Changed to be consistent with M42A. -Kaga
- scatter = SCATTER_AMOUNT_TIER_10 //Was previously 8, changed to be consistent with the XM42B.
- damage_mult = BASE_BULLET_DAMAGE_MULT
- recoil = RECOIL_AMOUNT_TIER_1
-
-/obj/item/weapon/gun/rifle/sniper/elite/simulate_recoil(total_recoil = 0, mob/user, atom/target)
- . = ..()
- if(.)
- var/mob/living/carbon/human/PMC_sniper = user
- if(PMC_sniper.lying == 0 && !istype(PMC_sniper.wear_suit,/obj/item/clothing/suit/storage/marine/smartgunner/veteran/pmc) && !istype(PMC_sniper.wear_suit,/obj/item/clothing/suit/storage/marine/veteran))
- PMC_sniper.visible_message(SPAN_WARNING("[PMC_sniper] is blown backwards from the recoil of the [src.name]!"),SPAN_HIGHDANGER("You are knocked prone by the blowback!"))
- step(PMC_sniper,turn(PMC_sniper.dir,180))
- PMC_sniper.apply_effect(5, WEAKEN)
-
-//Type 88 //Based on the actual Dragunov DMR rifle.
-
-/obj/item/weapon/gun/rifle/sniper/svd
- name = "\improper Type 88 designated marksman rifle"
- desc = "The standard issue DMR of the UPP, the Type 88 is sought after by competitive shooters and terrorists alike for its high degree of accuracy. Typically loaded with armor-piercing 7.62x54mmR rounds in a 12 round magazine."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/upp.dmi'
- icon_state = "type88"
- item_state = "type88"
-
- fire_sound = 'sound/weapons/gun_mg.ogg'
- current_mag = /obj/item/ammo_magazine/sniper/svd
- attachable_allowed = list(
- //Muzzle,
- /obj/item/attachable/bayonet,
- /obj/item/attachable/bayonet/upp_replica,
- /obj/item/attachable/bayonet/upp,
- //Under,
- /obj/item/attachable/verticalgrip,
- /obj/item/attachable/bipod,
- //Integrated,
- /obj/item/attachable/type88_barrel,
- )
- has_aimed_shot = FALSE
- flags_gun_features = GUN_AUTO_EJECTOR|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER|GUN_CAN_POINTBLANK
- starting_attachment_types = list()
- sniper_beam_type = null
- skill_locked = FALSE
-
-/obj/item/weapon/gun/rifle/sniper/svd/handle_starting_attachment()
- ..()
- var/obj/item/attachable/attachie = new /obj/item/attachable/type88_barrel(src)
- attachie.flags_attach_features &= ~ATTACH_REMOVABLE
- attachie.Attach(src)
- update_attachable(attachie.slot)
-
- var/obj/item/attachable/scope/variable_zoom/integrated/type88sight = new(src)
- type88sight.flags_attach_features &= ~ATTACH_REMOVABLE
- type88sight.hidden = TRUE
- type88sight.Attach(src)
- update_attachable(type88sight.slot)
-
-/obj/item/weapon/gun/rifle/sniper/svd/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 17,"rail_x" = 13, "rail_y" = 19, "under_x" = 26, "under_y" = 14, "stock_x" = 24, "stock_y" = 13, "special_x" = 39, "special_y" = 18)
-
-/obj/item/weapon/gun/rifle/sniper/svd/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_6)
- set_burst_amount(BURST_AMOUNT_TIER_1)
- accuracy_mult = BASE_ACCURACY_MULT * 3
- scatter = SCATTER_AMOUNT_TIER_8
- damage_mult = BASE_BULLET_DAMAGE_MULT
- recoil = RECOIL_AMOUNT_TIER_5
- damage_falloff_mult = 0
-
-//M4RA custom marksman rifle
-
-/obj/item/weapon/gun/rifle/m4ra_custom
- name = "\improper M4RA custom battle rifle"
- desc = "This is a further improvement upon the already rock-solid M4RA. Made by the USCM armorers on Chinook station - This variant of the M4RA has a specifically milled magazine well to accept A19 rounds. It sports a light-weight titantium-alloy frame, better responsive to the heavy kick of the tailor-made A19 rounds."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "m4ra_custom"
- item_state = "m4ra_custom"
- unacidable = TRUE
- indestructible = 1
- accepted_ammo = list(
- /obj/item/ammo_magazine/rifle/m4ra,
- /obj/item/ammo_magazine/rifle/m4ra/ap,
- /obj/item/ammo_magazine/rifle/m4ra/ext,
- /obj/item/ammo_magazine/rifle/m4ra/rubber,
- /obj/item/ammo_magazine/rifle/m4ra/incendiary,
- /obj/item/ammo_magazine/rifle/m4ra/heap,
- /obj/item/ammo_magazine/rifle/m4ra/penetrating,
- /obj/item/ammo_magazine/rifle/m4ra/custom,
- /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary,
- /obj/item/ammo_magazine/rifle/m4ra/custom/impact,
-
- )
-
- fire_sound = 'sound/weapons/gun_m4ra.ogg'
- reload_sound = 'sound/weapons/handling/l42_reload.ogg'
- unload_sound = 'sound/weapons/handling/l42_unload.ogg'
- current_mag = /obj/item/ammo_magazine/rifle/m4ra/custom
- force = 26
- attachable_allowed = list(
- /obj/item/attachable/suppressor,
- /obj/item/attachable/bayonet,
- /obj/item/attachable/bayonet/upp,
- /obj/item/attachable/bayonet/co2,
- /obj/item/attachable/reddot,
- /obj/item/attachable/reflex,
- /obj/item/attachable/flashlight,
- /obj/item/attachable/extended_barrel,
- /obj/item/attachable/magnetic_harness,
- /obj/item/attachable/bipod,
- /obj/item/attachable/attached_gun/shotgun,
- /obj/item/attachable/verticalgrip,
- /obj/item/attachable/angledgrip,
- /obj/item/attachable/lasersight,
- /obj/item/attachable/scope,
- /obj/item/attachable/scope/mini,
- /obj/item/attachable/flashlight/grip,
- )
-
- flags_gun_features = GUN_AUTO_EJECTOR|GUN_SPECIALIST|GUN_CAN_POINTBLANK|GUN_AMMO_COUNTER
- map_specific_decoration = TRUE
- aim_slowdown = SLOWDOWN_ADS_QUICK
- flags_item = TWOHANDED|NO_CRYO_STORE
-
-/obj/item/weapon/gun/rifle/m4ra_custom/handle_starting_attachment()
- ..()
- var/obj/item/attachable/m4ra_barrel_custom/integrated = new(src)
- integrated.flags_attach_features &= ~ATTACH_REMOVABLE
- integrated.Attach(src)
- update_attachable(integrated.slot)
-
-
-/obj/item/weapon/gun/rifle/m4ra_custom/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 43, "muzzle_y" = 17,"rail_x" = 23, "rail_y" = 21, "under_x" = 30, "under_y" = 11, "stock_x" = 24, "stock_y" = 13, "special_x" = 37, "special_y" = 16)
-
-/obj/item/weapon/gun/rifle/m4ra_custom/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_6)
- set_burst_amount(BURST_AMOUNT_TIER_2)
- set_burst_delay(FIRE_DELAY_TIER_12)
- accuracy_mult = BASE_ACCURACY_MULT + HIT_ACCURACY_MULT_TIER_2
- scatter = SCATTER_AMOUNT_TIER_8
- burst_scatter_mult = SCATTER_AMOUNT_TIER_8
- damage_mult = BASE_BULLET_DAMAGE_MULT + BULLET_DAMAGE_MULT_TIER_2
- recoil = RECOIL_AMOUNT_TIER_5
- damage_falloff_mult = 0
-
-/obj/item/weapon/gun/rifle/m4ra_custom/able_to_fire(mob/living/user)
- . = ..()
- if (. && istype(user)) //Let's check all that other stuff first.
- if(!skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_SCOUT)
- to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
- return FALSE
-
-//-------------------------------------------------------
-//HEAVY WEAPONS
-
-/obj/item/weapon/gun/launcher
- gun_category = GUN_CATEGORY_HEAVY
- has_empty_icon = FALSE
- has_open_icon = FALSE
- ///gun update_icon doesn't detect that guns with no magazine are loaded or not, and will always append _o or _e if possible.
- var/GL_has_empty_icon = TRUE
- ///gun update_icon doesn't detect that guns with no magazine are loaded or not, and will always append _o or _e if possible.
- var/GL_has_open_icon = FALSE
-
- ///Internal storage item used as magazine. Must be initialised to work! Set parameters by variables or it will inherit standard numbers from storage.dm. Got to call it *something* and 'magazine' or w/e would be confusing.
- var/obj/item/storage/internal/cylinder
- /// Variable that initializes the above.
- var/has_cylinder = FALSE
- ///What single item to fill the storage with, if any. This does not respect w_class.
- var/preload
- ///How many items can be inserted. "Null" = backpack-style size-based inventory. You'll have to set max_storage_space too if you do that, and arrange any initial contents. Iff you arrange to put in more items than the storage can hold, they can be taken out but not replaced.
- var/internal_slots
- ///how big an item can be inserted.
- var/internal_max_w_class
- ///the sfx played when the storage is opened.
- var/use_sound = null
- ///Whether clicking a held weapon with an empty hand will open its inventory or draw a munition out.
- var/direct_draw = TRUE
-
-/obj/item/weapon/gun/launcher/Initialize(mapload, spawn_empty) //If changing vars on init, be sure to do the parent proccall *after* the change.
- . = ..()
- if(has_cylinder)
- cylinder = new /obj/item/storage/internal(src)
- cylinder.storage_slots = internal_slots
- cylinder.max_w_class = internal_max_w_class
- cylinder.use_sound = use_sound
- if(direct_draw)
- cylinder.storage_flags ^= STORAGE_USING_DRAWING_METHOD
- if(preload && !spawn_empty) for(var/i = 1 to cylinder.storage_slots)
- new preload(cylinder)
- update_icon()
-
-/obj/item/weapon/gun/launcher/verb/toggle_draw_mode()
- set name = "Switch Storage Drawing Method"
- set category = "Object"
- set src in usr
-
- cylinder.storage_draw_logic(src.name)
-
-//-------------------------------------------------------
-//GRENADE LAUNCHER
-
-/obj/item/weapon/gun/launcher/grenade //Parent item for GLs.
- w_class = SIZE_LARGE
- throw_speed = SPEED_SLOW
- throw_range = 10
- force = 5
-
- fire_sound = 'sound/weapons/armbomb.ogg'
- cocked_sound = 'sound/weapons/gun_m92_cocked.ogg'
- reload_sound = 'sound/weapons/gun_shotgun_open2.ogg' //Played when inserting nade.
- unload_sound = 'sound/weapons/gun_revolver_unload.ogg'
-
- has_cylinder = TRUE //This weapon won't work otherwise.
- preload = /obj/item/explosive/grenade/high_explosive
- internal_slots = 1 //This weapon must use slots.
- internal_max_w_class = SIZE_MEDIUM //MEDIUM = M15.
-
- aim_slowdown = SLOWDOWN_ADS_SPECIALIST
- wield_delay = WIELD_DELAY_SLOW
- flags_gun_features = GUN_UNUSUAL_DESIGN|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY
- ///Can you access the storage by clicking it, put things into it, or take things out? Meant for break-actions mostly but useful for any state where you want access to be toggleable. Make sure to call cylinder.close(user) so they don't still have the screen open!
- var/open_chamber = TRUE
- ///Does it launch its grenades in a low arc or a high? Do they strike people in their path, or fly beyond?
- var/is_lobbing = FALSE
- ///Verboten munitions. This is a blacklist. Anything in this list isn't loadable.
- var/disallowed_grenade_types = list(/obj/item/explosive/grenade/spawnergrenade, /obj/item/explosive/grenade/alien, /obj/item/explosive/grenade/incendiary/molotov, /obj/item/explosive/grenade/flashbang)
- ///What is this weapon permitted to fire? This is a whitelist. Anything in this list can be fired. Anything.
- var/valid_munitions = list(/obj/item/explosive/grenade)
-
-
-/obj/item/weapon/gun/launcher/grenade/set_gun_config_values()
- ..()
- recoil = RECOIL_AMOUNT_TIER_4 //Same as m37 shotgun.
-
-
-/obj/item/weapon/gun/launcher/grenade/on_pocket_insertion() //Plays load sfx whenever a nade is put into storage.
- playsound(usr, reload_sound, 25, 1)
- update_icon()
-
-/obj/item/weapon/gun/launcher/grenade/on_pocket_removal()
- update_icon()
-
-/obj/item/weapon/gun/launcher/grenade/get_examine_text(mob/user) //Different treatment for single-shot VS multi-shot GLs.
- . = ..()
- if(get_dist(user, src) > 2 && user != loc)
- return
- if(length(cylinder.contents))
- if(internal_slots == 1)
- . += SPAN_NOTICE("It is loaded with a grenade.")
- else
- . += SPAN_NOTICE("It is loaded with [length(cylinder.contents)] / [internal_slots] grenades.")
- else
- . += SPAN_NOTICE("It is empty.")
-
-
-/obj/item/weapon/gun/launcher/grenade/update_icon()
- ..()
- var/GL_sprite = base_gun_icon
- if(GL_has_empty_icon && cylinder && !length(cylinder.contents))
- GL_sprite += "_e"
- playsound(loc, cocked_sound, 25, 1)
- if(GL_has_open_icon && open_chamber)
- GL_sprite += "_o"
- playsound(loc, cocked_sound, 25, 1)
- icon_state = GL_sprite
-
-
-/obj/item/weapon/gun/launcher/grenade/attack_hand(mob/user)
- if(!open_chamber || src != user.get_inactive_hand()) //Need to have the GL in your hands to open the cylinder.
- return ..()
- if(cylinder.handle_attack_hand(user))
- ..()
-
-
-/obj/item/weapon/gun/launcher/grenade/unload(mob/user, reload_override = FALSE, drop_override = FALSE, loc_override = FALSE)
- if(!open_chamber)
- to_chat(user, SPAN_WARNING("[src] is closed!"))
- return
- if(!length(cylinder.contents))
- to_chat(user, SPAN_WARNING("It's empty!"))
- return
-
- var/obj/item/explosive/grenade/nade = cylinder.contents[length(cylinder.contents)] //Grab the last-inserted one. Or the only one, as the case may be.
- cylinder.remove_from_storage(nade, user.loc)
-
- if(drop_override || !user)
- nade.forceMove(get_turf(src))
- else
- user.put_in_hands(nade)
-
- user.visible_message(SPAN_NOTICE("[user] unloads [nade] from [src]."),
- SPAN_NOTICE("You unload [nade] from [src]."), null, 4, CHAT_TYPE_COMBAT_ACTION)
- playsound(user, unload_sound, 30, 1)
-
-
-/obj/item/weapon/gun/launcher/grenade/attackby(obj/item/I, mob/user)
- if(istype(I,/obj/item/attachable) && check_inactive_hand(user))
- attach_to_gun(user,I)
- return
- return cylinder.attackby(I, user)
-
-/obj/item/weapon/gun/launcher/grenade/unique_action(mob/user)
- if(isobserver(usr) || isxeno(usr))
- return
- if(locate(/datum/action/item_action/toggle_firing_level) in actions)
- toggle_firing_level(usr)
-
-/obj/item/weapon/gun/launcher/grenade/proc/allowed_ammo_type(obj/item/I)
- for(var/G in disallowed_grenade_types) //Check for the bad stuff.
- if(istype(I, G))
- return FALSE
- for(var/G in valid_munitions) //Check if it has a ticket.
- if(istype(I, G))
- return TRUE
-
-
-/obj/item/weapon/gun/launcher/grenade/on_pocket_attackby(obj/item/explosive/grenade/I, mob/user) //the attack in question is on the internal container. Complete override - normal storage attackby cannot be silenced, and will always say "you put the x into y".
- if(!open_chamber)
- to_chat(user, SPAN_WARNING("[src] is closed!"))
- return
- if(!istype(I))
- to_chat(user, SPAN_WARNING("You can't load [I] into [src]!"))
- return
- if(!allowed_ammo_type(I))
- to_chat(user, SPAN_WARNING("[src] can't fire this type of grenade!"))
- return
- if(length(cylinder.contents) >= internal_slots)
- to_chat(user, SPAN_WARNING("[src] cannot hold more grenades!"))
- return
- if(!cylinder.can_be_inserted(I, user)) //Technically includes whether there's room for it, but the above gives a tailored message.
- return
-
- user.visible_message(SPAN_NOTICE("[user] loads [I] into [src]."),
- SPAN_NOTICE("You load [I] into the grenade launcher."), null, 4, CHAT_TYPE_COMBAT_ACTION)
- playsound(usr, reload_sound, 75, 1)
- if(internal_slots > 1)
- to_chat(user, SPAN_INFO("Now storing: [length(cylinder.contents) + 1] / [internal_slots] grenades."))
-
- cylinder.handle_item_insertion(I, TRUE, user)
-
-
-/obj/item/weapon/gun/launcher/grenade/able_to_fire(mob/living/user) //Skillchecks and fire blockers go in the child items.
- . = ..()
- if(.)
- if(!length(cylinder.contents))
- to_chat(user, SPAN_WARNING("The [name] is empty."))
- return FALSE
- var/obj/item/explosive/grenade/G = cylinder.contents[1]
- if(G.antigrief_protection && user.faction == FACTION_MARINE && explosive_antigrief_check(G, user))
- to_chat(user, SPAN_WARNING("\The [name]'s safe-area accident inhibitor prevents you from firing!"))
- msg_admin_niche("[key_name(user)] attempted to prime \a [G.name] in [get_area(src)] [ADMIN_JMP(src.loc)]")
- return FALSE
-
-
-/obj/item/weapon/gun/launcher/grenade/afterattack(atom/target, mob/user, flag) //Not actually after the attack. After click, more like.
- if(able_to_fire(user))
- if(get_dist(target,user) <= 2)
- var/obj/item/explosive/grenade/nade = cylinder.contents[1]
- if(nade.dangerous)
- to_chat(user, SPAN_WARNING("The grenade launcher beeps a warning noise. You are too close!"))
- return
- fire_grenade(target,user)
-
-
-/obj/item/weapon/gun/launcher/grenade/proc/fire_grenade(atom/target, mob/user)
- set waitfor = 0
- last_fired = world.time
-
- var/to_firer = "You fire the [name]!"
- if(internal_slots > 1)
- to_firer += " [length(cylinder.contents)-1]/[internal_slots] grenades remaining."
- user.visible_message(SPAN_DANGER("[user] fired a grenade!"),
- SPAN_WARNING("[to_firer]"), message_flags = CHAT_TYPE_WEAPON_USE)
- playsound(user.loc, fire_sound, 50, 1)
-
- var/angle = round(Get_Angle(user,target))
- muzzle_flash(angle,user)
- simulate_recoil(0, user)
-
- var/obj/item/explosive/grenade/fired = cylinder.contents[1]
- cylinder.remove_from_storage(fired, user.loc)
- var/pass_flags = NO_FLAGS
- if(is_lobbing)
- if(istype(fired, /obj/item/explosive/grenade/slug/baton))
- if(ishuman(user))
- var/mob/living/carbon/human/human_user = user
- human_user.remember_dropped_object(fired)
- fired.fingerprintslast = key_name(user)
- pass_flags |= PASS_MOB_THRU_HUMAN|PASS_MOB_IS_OTHER|PASS_OVER
- else
- pass_flags |= PASS_MOB_THRU|PASS_HIGH_OVER
-
- msg_admin_attack("[key_name_admin(user)] fired a grenade ([fired.name]) from \a ([name]).")
- log_game("[key_name_admin(user)] used a grenade ([name]).")
-
- fired.throw_range = 20
- fired.det_time = min(10, fired.det_time)
- fired.activate(user, FALSE)
- fired.forceMove(get_turf(src))
- fired.throw_atom(target, 20, SPEED_VERY_FAST, user, null, NORMAL_LAUNCH, pass_flags)
-
-
-
-//Doesn't use these. Listed for reference.
-/obj/item/weapon/gun/launcher/grenade/load_into_chamber()
- return
-/obj/item/weapon/gun/launcher/grenade/reload_into_chamber()
- return
-
-/obj/item/weapon/gun/launcher/grenade/has_ammunition()
- return length(cylinder.contents)
-
-//-------------------------------------------------------
-//Toggle firing level special action for grenade launchers
-
-/datum/action/item_action/toggle_firing_level/New(Target, obj/item/holder)
- . = ..()
- name = "Toggle Firing Level"
- button.name = name
- update_icon()
-
-/datum/action/item_action/toggle_firing_level/action_activate()
- var/obj/item/weapon/gun/launcher/grenade/G = holder_item
- if(!ishuman(owner))
- return
- var/mob/living/carbon/human/H = owner
- if(H.is_mob_incapacitated() || G.get_active_firearm(H, FALSE) != holder_item)
- return
- G.toggle_firing_level(usr)
-
-/datum/action/item_action/toggle_firing_level/proc/update_icon()
- var/obj/item/weapon/gun/launcher/grenade/G = holder_item
- if(G.is_lobbing)
- action_icon_state = "hightoss_on"
- else
- action_icon_state = "hightoss_off"
- button.overlays.Cut()
- button.overlays += image('icons/mob/hud/actions.dmi', button, action_icon_state)
-
-/obj/item/weapon/gun/launcher/grenade/proc/toggle_firing_level(mob/user)
- is_lobbing = !is_lobbing
- to_chat(user, "[icon2html(src, usr)] You changed \the [src]'s firing level. You will now fire [is_lobbing ? "in an arcing path over obstacles" : "directly at your target"].")
- playsound(loc,'sound/machines/click.ogg', 25, 1)
- var/datum/action/item_action/toggle_firing_level/TFL = locate(/datum/action/item_action/toggle_firing_level) in actions
- TFL.update_icon()
-
-//-------------------------------------------------------
-//M92 GRENADE LAUNCHER
-
-/obj/item/weapon/gun/launcher/grenade/m92
- name = "\improper M92 grenade launcher"
- desc = "A heavy, 6-shot grenade launcher used by the Colonial Marines for area denial and big explosions."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "m92"
- item_state = "m92"
- unacidable = TRUE
- indestructible = 1
- matter = list("metal" = 6000)
- actions_types = list(/datum/action/item_action/toggle_firing_level)
-
- attachable_allowed = list(/obj/item/attachable/magnetic_harness)
- flags_item = TWOHANDED|NO_CRYO_STORE
- map_specific_decoration = TRUE
-
- is_lobbing = TRUE
- internal_slots = 6
- direct_draw = FALSE
-
-/obj/item/weapon/gun/launcher/grenade/m92/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 14, "rail_y" = 22, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
-
-/obj/item/weapon/gun/launcher/grenade/m92/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_4*4)
-
-/obj/item/weapon/gun/launcher/grenade/m92/able_to_fire(mob/living/user)
- . = ..()
- if (. && istype(user))
- if(!skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_GRENADIER)
- to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
- return FALSE
-
-
-//-------------------------------------------------------
-//M81 GRENADE LAUNCHER
-
-/obj/item/weapon/gun/launcher/grenade/m81
- name = "\improper M81 grenade launcher"
- desc = "A lightweight, single-shot low-angle grenade launcher used by the Colonial Marines for area denial and big explosions."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/colony.dmi'
- icon_state = "m81"
- item_state = "m81" //needs a wield sprite.
-
- matter = list("metal" = 7000)
-
-/obj/item/weapon/gun/launcher/grenade/m81/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 14, "rail_y" = 22, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
-
-/obj/item/weapon/gun/launcher/grenade/m81/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_4 * 1.5)
-
-/obj/item/weapon/gun/launcher/grenade/m81/on_pocket_removal()
- ..()
- playsound(usr, unload_sound, 30, 1)
-
-/obj/item/weapon/gun/launcher/grenade/m81/riot/able_to_fire(mob/living/user)
- . = ..()
- if (. && istype(user))
- if(!skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED))
- to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
- return FALSE
-
-
-/obj/item/weapon/gun/launcher/grenade/m81/riot
- name = "\improper M81 riot grenade launcher"
- desc = "A lightweight, single-shot low-angle grenade launcher to launch tear gas grenades. Used by the Colonial Marines Military Police during riots."
- valid_munitions = list(/obj/item/explosive/grenade/custom/teargas)
- preload = /obj/item/explosive/grenade/custom/teargas
-
-//-------------------------------------------------------
-//M79 Grenade Launcher subtype of the M81
-
-/obj/item/weapon/gun/launcher/grenade/m81/m79//m79 variant for marines
- name = "\improper M79 grenade launcher"
- desc = "A heavy, low-angle 40mm grenade launcher. It's been in use since the Vietnam War, though this version has been modernized with an IFF enabled micro-computer. The wooden furniture is, in fact, made of painted hardened polykevlon."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "m79"
- item_state = "m79"
- flags_equip_slot = SLOT_BACK
- preload = /obj/item/explosive/grenade/slug/baton
- is_lobbing = TRUE
- actions_types = list(/datum/action/item_action/toggle_firing_level)
-
- fire_sound = 'sound/weapons/handling/m79_shoot.ogg'
- cocked_sound = 'sound/weapons/handling/m79_break_open.ogg'
- reload_sound = 'sound/weapons/handling/m79_reload.ogg'
- unload_sound = 'sound/weapons/handling/m79_unload.ogg'
-
- attachable_allowed = list(
- /obj/item/attachable/magnetic_harness,
- /obj/item/attachable/flashlight,
- /obj/item/attachable/reddot,
- /obj/item/attachable/reflex,
- /obj/item/attachable/stock/m79,
- )
-
-/obj/item/weapon/gun/launcher/grenade/m81/m79/handle_starting_attachment()
- ..()
- var/obj/item/attachable/stock/m79/S = new(src)
- S.hidden = FALSE
- S.flags_attach_features &= ~ATTACH_REMOVABLE
- S.Attach(src)
- update_attachable(S.slot)
-
-/obj/item/weapon/gun/launcher/grenade/m81/m79/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 9, "rail_y" = 22, "under_x" = 19, "under_y" = 14, "stock_x" = 14, "stock_y" = 14)
-
-/obj/item/weapon/gun/launcher/grenade/m81/m79/set_bullet_traits()
- LAZYADD(traits_to_give, list(
- BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)//might not need this because of is_lobbing, but let's keep it just incase
- ))
-
-//-------------------------------------------------------
-//M5 RPG
-
-/obj/item/weapon/gun/launcher/rocket
- name = "\improper M5 RPG"
- desc = "The M5 RPG is the primary anti-armor weapon of the USCM. Used to take out light-tanks and enemy structures, the M5 RPG is a dangerous weapon with a variety of combat uses."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "m5"
- item_state = "m5"
- unacidable = TRUE
- indestructible = 1
-
- matter = list("metal" = 10000)
- current_mag = /obj/item/ammo_magazine/rocket
- flags_equip_slot = NO_FLAGS
- w_class = SIZE_HUGE
- force = 15
- wield_delay = WIELD_DELAY_HORRIBLE
- delay_style = WEAPON_DELAY_NO_FIRE
- aim_slowdown = SLOWDOWN_ADS_SPECIALIST
- attachable_allowed = list(
- /obj/item/attachable/magnetic_harness,
- )
-
- flags_gun_features = GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_INTERNAL_MAG
- var/datum/effect_system/smoke_spread/smoke
-
- flags_item = TWOHANDED|NO_CRYO_STORE
- var/skill_locked = TRUE
-
-/obj/item/weapon/gun/launcher/rocket/Initialize(mapload, spawn_empty)
- . = ..()
- smoke = new()
- smoke.attach(src)
-
-/obj/item/weapon/gun/launcher/rocket/Destroy()
- QDEL_NULL(smoke)
- return ..()
-
-
-/obj/item/weapon/gun/launcher/rocket/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 6, "rail_y" = 19, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
-
-
-/obj/item/weapon/gun/launcher/rocket/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_6*2)
- accuracy_mult = BASE_ACCURACY_MULT
- scatter = SCATTER_AMOUNT_TIER_6
- damage_mult = BASE_BULLET_DAMAGE_MULT
- recoil = RECOIL_AMOUNT_TIER_3
-
-
-/obj/item/weapon/gun/launcher/rocket/get_examine_text(mob/user)
- . = ..()
- if(current_mag.current_rounds <= 0)
- . += "It's not loaded."
- return
- if(current_mag.current_rounds > 0)
- . += "It has an 84mm [ammo.name] loaded."
-
-
-/obj/item/weapon/gun/launcher/rocket/able_to_fire(mob/living/user)
- . = ..()
- if (. && istype(user)) //Let's check all that other stuff first.
- if(skill_locked && !skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_ROCKET)
- to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
- return 0
- if(user.faction == FACTION_MARINE && explosive_antigrief_check(src, user))
- to_chat(user, SPAN_WARNING("\The [name]'s safe-area accident inhibitor prevents you from firing!"))
- msg_admin_niche("[key_name(user)] attempted to fire \a [name] in [get_area(src)] [ADMIN_JMP(loc)]")
- return FALSE
- if(current_mag && current_mag.current_rounds > 0)
- make_rocket(user, 0, 1)
-
-/obj/item/weapon/gun/launcher/rocket/load_into_chamber(mob/user)
-// if(active_attachable) active_attachable = null
- return ready_in_chamber()
-
-//No such thing
-/obj/item/weapon/gun/launcher/rocket/reload_into_chamber(mob/user)
- return TRUE
-
-/obj/item/weapon/gun/launcher/rocket/delete_bullet(obj/projectile/projectile_to_fire, refund = 0)
- if(!current_mag)
- return
- qdel(projectile_to_fire)
- if(refund)
- current_mag.current_rounds++
- return TRUE
-
-/obj/item/weapon/gun/launcher/rocket/proc/make_rocket(mob/user, drop_override = 0, empty = 1)
- if(!current_mag)
- return
-
- var/obj/item/ammo_magazine/rocket/r = new current_mag.type()
- //if there's ever another type of custom rocket ammo this logic should just be moved into a function on the rocket
- if(istype(current_mag, /obj/item/ammo_magazine/rocket/custom) && !empty)
- //set the custom rocket variables here.
- var/obj/item/ammo_magazine/rocket/custom/k = new /obj/item/ammo_magazine/rocket/custom
- var/obj/item/ammo_magazine/rocket/custom/cur_mag_cast = current_mag
- k.contents = cur_mag_cast.contents
- k.desc = cur_mag_cast.desc
- k.fuel = cur_mag_cast.fuel
- k.icon_state = cur_mag_cast.icon_state
- k.warhead = cur_mag_cast.warhead
- k.locked = cur_mag_cast.locked
- k.name = cur_mag_cast.name
- k.filters = cur_mag_cast.filters
- r = k
-
- if(empty)
- r.current_rounds = 0
- if(drop_override || !user) //If we want to drop it on the ground or there's no user.
- r.forceMove(get_turf(src)) //Drop it on the ground.
- else
- user.put_in_hands(r)
- r.update_icon()
-
-/obj/item/weapon/gun/launcher/rocket/reload(mob/user, obj/item/ammo_magazine/rocket)
- if(!current_mag)
- return
- if(flags_gun_features & GUN_BURST_FIRING)
- return
-
- if(!rocket || !istype(rocket) || !istype(src, rocket.gun_type))
- to_chat(user, SPAN_WARNING("That's not going to fit!"))
- return
-
- if(current_mag.current_rounds > 0)
- to_chat(user, SPAN_WARNING("[src] is already loaded!"))
- return
-
- if(rocket.current_rounds <= 0)
- to_chat(user, SPAN_WARNING("That frame is empty!"))
- return
-
- if(user)
- to_chat(user, SPAN_NOTICE("You begin reloading [src]. Hold still..."))
- if(do_after(user,current_mag.reload_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
- qdel(current_mag)
- user.drop_inv_item_on_ground(rocket)
- current_mag = rocket
- rocket.forceMove(src)
- replace_ammo(,rocket)
- to_chat(user, SPAN_NOTICE("You load [rocket] into [src]."))
- if(reload_sound)
- playsound(user, reload_sound, 25, 1)
- else
- playsound(user,'sound/machines/click.ogg', 25, 1)
- else
- to_chat(user, SPAN_WARNING("Your reload was interrupted!"))
- return
- else
- qdel(current_mag)
- current_mag = rocket
- rocket.forceMove(src)
- replace_ammo(,rocket)
- return TRUE
-
-/obj/item/weapon/gun/launcher/rocket/unload(mob/user, reload_override = 0, drop_override = 0)
- if(user && current_mag)
- if(current_mag.current_rounds <= 0)
- to_chat(user, SPAN_WARNING("[src] is already empty!"))
- return
- to_chat(user, SPAN_NOTICE("You begin unloading [src]. Hold still..."))
- if(do_after(user,current_mag.reload_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
- if(current_mag.current_rounds <= 0)
- to_chat(user, SPAN_WARNING("You have already unloaded \the [src]."))
- return
- playsound(user, unload_sound, 25, 1)
- user.visible_message(SPAN_NOTICE("[user] unloads [ammo] from [src]."),
- SPAN_NOTICE("You unload [ammo] from [src]."))
- make_rocket(user, drop_override, 0)
- current_mag.current_rounds = 0
-
-//Adding in the rocket backblast. The tile behind the specialist gets blasted hard enough to down and slightly wound anyone
-/obj/item/weapon/gun/launcher/rocket/apply_bullet_effects(obj/projectile/projectile_to_fire, mob/user, i = 1, reflex = 0)
- . = ..()
- if(!HAS_TRAIT(user, TRAIT_EAR_PROTECTION) && ishuman(user))
- var/mob/living/carbon/human/huser = user
- to_chat(user, SPAN_WARNING("Augh!! \The [src]'s launch blast resonates extremely loudly in your ears! You probably should have worn some sort of ear protection..."))
- huser.apply_effect(6, STUTTER)
- huser.emote("pain")
- huser.SetEarDeafness(max(user.ear_deaf,10))
-
- var/backblast_loc = get_turf(get_step(user.loc, turn(user.dir, 180)))
- smoke.set_up(1, 0, backblast_loc, turn(user.dir, 180))
- smoke.start()
- playsound(src, 'sound/weapons/gun_rocketlauncher.ogg', 100, TRUE, 10)
- for(var/mob/living/carbon/C in backblast_loc)
- if(!C.lying && !HAS_TRAIT(C, TRAIT_EAR_PROTECTION)) //Have to be standing up to get the fun stuff
- C.apply_damage(15, BRUTE) //The shockwave hurts, quite a bit. It can knock unarmored targets unconscious in real life
- C.apply_effect(4, STUN) //For good measure
- C.apply_effect(6, STUTTER)
- C.emote("pain")
-
-//-------------------------------------------------------
-//M5 RPG'S MEAN FUCKING COUSIN
-
-/obj/item/weapon/gun/launcher/rocket/m57a4
- name = "\improper M57-A4 'Lightning Bolt' quad thermobaric launcher"
- desc = "The M57-A4 'Lightning Bolt' is possibly the most destructive man-portable weapon ever made. It is a 4-barreled missile launcher capable of burst-firing 4 thermobaric missiles. Enough said."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/event.dmi'
- icon_state = "m57a4"
- item_state = "m57a4"
-
- current_mag = /obj/item/ammo_magazine/rocket/m57a4
- aim_slowdown = SLOWDOWN_ADS_SUPERWEAPON
- flags_gun_features = GUN_WIELDED_FIRING_ONLY
-
-/obj/item/weapon/gun/launcher/rocket/m57a4/set_gun_config_values()
- ..()
- set_fire_delay(FIRE_DELAY_TIER_5)
- set_burst_delay(FIRE_DELAY_TIER_7)
- set_burst_amount(BURST_AMOUNT_TIER_4)
- accuracy_mult = BASE_ACCURACY_MULT - HIT_ACCURACY_MULT_TIER_4
- scatter = SCATTER_AMOUNT_TIER_6
- damage_mult = BASE_BULLET_DAMAGE_MULT
- recoil = RECOIL_AMOUNT_TIER_3
-
-
-//-------------------------------------------------------
-//AT rocket launchers, can be used by non specs
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank //reloadable
- name = "\improper QH-4 Shoulder-Mounted Anti-Tank RPG"
- desc = "Used to take out light-tanks and enemy structures, the QH-4 is a dangerous weapon specialised against vehicles. Requires direct hits to penetrate vehicle armor."
- icon_state = "m83a2"
- item_state = "m83a2"
- unacidable = FALSE
- indestructible = FALSE
- skill_locked = FALSE
-
- current_mag = /obj/item/ammo_magazine/rocket/anti_tank
-
- attachable_allowed = list()
-
- flags_gun_features = GUN_WIELDED_FIRING_ONLY
-
- flags_item = TWOHANDED
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/set_bullet_traits()
- . = ..()
- LAZYADD(traits_to_give, list(
- BULLET_TRAIT_ENTRY_ID("vehicles", /datum/element/bullet_trait_damage_boost, 20, GLOB.damage_boost_vehicles),
- ))
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable //single shot and disposable
- name = "\improper M83A2 SADAR"
- desc = "The M83A2 SADAR is a lightweight one-shot anti-armor weapon capable of engaging enemy vehicles at ranges up to 1,000m. Fully disposable, the rocket's launcher is discarded after firing. When stowed (unique-action), the SADAR system consists of a watertight carbon-fiber composite blast tube, inside of which is an aluminum launch tube containing the missile. The weapon is fired by pushing a charge button on the trigger grip. It is sighted and fired from the shoulder."
- var/fired = FALSE
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/get_examine_text(mob/user)
- . = ..()
- . += SPAN_NOTICE("You can fold it up with unique-action.")
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/Fire(atom/target, mob/living/user, params, reflex, dual_wield)
- . = ..()
- if(.)
- fired = TRUE
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/unique_action(mob/M)
- if(fired)
- to_chat(M, SPAN_WARNING("\The [src] has already been fired - you can't fold it back up again!"))
- return
-
- M.visible_message(SPAN_NOTICE("[M] begins to fold up \the [src]."), SPAN_NOTICE("You start to fold and collapse closed \the [src]."))
-
- if(!do_after(M, 2 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC))
- to_chat(M, SPAN_NOTICE("You stop folding up \the [src]"))
- return
-
- fold(M)
- M.visible_message(SPAN_NOTICE("[M] finishes folding \the [src]."), SPAN_NOTICE("You finish folding \the [src]."))
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/proc/fold(mob/user)
- var/obj/item/prop/folded_anti_tank_sadar/F = new /obj/item/prop/folded_anti_tank_sadar(src.loc)
- transfer_label_component(F)
- qdel(src)
- user.put_in_active_hand(F)
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/reload()
- to_chat(usr, SPAN_WARNING("You cannot reload \the [src]!"))
- return
-
-/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/unload()
- to_chat(usr, SPAN_WARNING("You cannot unload \the [src]!"))
- return
-
-//folded version of the sadar
-/obj/item/prop/folded_anti_tank_sadar
- name = "\improper M83 SADAR (folded)"
- desc = "An M83 SADAR Anti-Tank RPG, compacted for easier storage. Can be unfolded with the Z key."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
- icon_state = "m83a2_folded"
- w_class = SIZE_MEDIUM
- garbage = FALSE
-
-/obj/item/prop/folded_anti_tank_sadar/attack_self(mob/user)
- user.visible_message(SPAN_NOTICE("[user] begins to unfold \the [src]."), SPAN_NOTICE("You start to unfold and expand \the [src]."))
- playsound(src, 'sound/items/component_pickup.ogg', 20, TRUE, 5)
-
- if(!do_after(user, 4 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC))
- to_chat(user, SPAN_NOTICE("You stop unfolding \the [src]"))
- return
-
- unfold(user)
-
- user.visible_message(SPAN_NOTICE("[user] finishes unfolding \the [src]."), SPAN_NOTICE("You finish unfolding \the [src]."))
- playsound(src, 'sound/items/component_pickup.ogg', 20, TRUE, 5)
- . = ..()
-
-/obj/item/prop/folded_anti_tank_sadar/proc/unfold(mob/user)
- var/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/F = new /obj/item/weapon/gun/launcher/rocket/anti_tank/disposable(src.loc)
- transfer_label_component(F)
- qdel(src)
- user.put_in_active_hand(F)
-
-//-------------------------------------------------------
-//UPP Rocket Launcher
-
-/obj/item/weapon/gun/launcher/rocket/upp
- name = "\improper HJRA-12 Handheld Anti-Tank Grenade Launcher"
- desc = "The HJRA-12 Handheld Anti-Tank Grenade Launcher is the standard Anti-Armor weapon of the UPP. It is designed to be easy to use and to take out or disable armored vehicles."
- icon = 'icons/obj/items/weapons/guns/guns_by_faction/upp.dmi'
- icon_state = "hjra12"
- item_state = "hjra12"
- skill_locked = FALSE
- current_mag = /obj/item/ammo_magazine/rocket/upp/at
-
- attachable_allowed = list(/obj/item/attachable/upp_rpg_breech)
-
- flags_gun_features = GUN_WIELDED_FIRING_ONLY
-
- flags_item = TWOHANDED
-
-/obj/item/weapon/gun/launcher/rocket/upp/set_gun_attachment_offsets()
- attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 6, "rail_y" = 19, "under_x" = 19, "under_y" = 14, "stock_x" = -6, "stock_y" = 16, "special_x" = 37, "special_y" = 16)
-
-/obj/item/weapon/gun/launcher/rocket/upp/handle_starting_attachment()
- ..()
- var/obj/item/attachable/upp_rpg_breech/S = new(src)
- S.flags_attach_features &= ~ATTACH_REMOVABLE
- S.Attach(src)
- update_attachables()
-
- var/obj/item/attachable/magnetic_harness/Integrated = new(src)
- Integrated.hidden = TRUE
- Integrated.flags_attach_features &= ~ATTACH_REMOVABLE
- Integrated.Attach(src)
- update_attachable(Integrated.slot)
-
-/obj/item/weapon/gun/launcher/rocket/upp/apply_bullet_effects(obj/projectile/projectile_to_fire, mob/user, i = 1, reflex = 0)
- . = ..()
- if(!HAS_TRAIT(user, TRAIT_EAR_PROTECTION) && ishuman(user))
- return
-
- var/backblast_loc = get_turf(get_step(user.loc, turn(user.dir, 180)))
- smoke.set_up(1, 0, backblast_loc, turn(user.dir, 180))
- smoke.start()
- playsound(src, 'sound/weapons/gun_rocketlauncher.ogg', 100, TRUE, 10)
- for(var/mob/living/carbon/C in backblast_loc)
- if(!C.lying && !HAS_TRAIT(C, TRAIT_EAR_PROTECTION)) //Have to be standing up to get the fun stuff
- C.apply_damage(15, BRUTE) //The shockwave hurts, quite a bit. It can knock unarmored targets unconscious in real life
- C.apply_effect(4, STUN) //For good measure
- C.apply_effect(6, STUTTER)
- C.emote("pain")
diff --git a/code/modules/projectiles/guns/specialist/launcher/grenade_launcher.dm b/code/modules/projectiles/guns/specialist/launcher/grenade_launcher.dm
new file mode 100644
index 000000000000..0f767d679d03
--- /dev/null
+++ b/code/modules/projectiles/guns/specialist/launcher/grenade_launcher.dm
@@ -0,0 +1,364 @@
+//-------------------------------------------------------
+//GRENADE LAUNCHER
+
+/obj/item/weapon/gun/launcher/grenade //Parent item for GLs.
+ w_class = SIZE_LARGE
+ throw_speed = SPEED_SLOW
+ throw_range = 10
+ force = 5
+
+ fire_sound = 'sound/weapons/armbomb.ogg'
+ cocked_sound = 'sound/weapons/gun_m92_cocked.ogg'
+ reload_sound = 'sound/weapons/gun_shotgun_open2.ogg' //Played when inserting nade.
+ unload_sound = 'sound/weapons/gun_revolver_unload.ogg'
+
+ has_cylinder = TRUE //This weapon won't work otherwise.
+ preload = /obj/item/explosive/grenade/high_explosive
+ internal_slots = 1 //This weapon must use slots.
+ internal_max_w_class = SIZE_MEDIUM //MEDIUM = M15.
+
+ aim_slowdown = SLOWDOWN_ADS_SPECIALIST
+ wield_delay = WIELD_DELAY_SLOW
+ flags_gun_features = GUN_UNUSUAL_DESIGN|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY
+ ///Can you access the storage by clicking it, put things into it, or take things out? Meant for break-actions mostly but useful for any state where you want access to be toggleable. Make sure to call cylinder.close(user) so they don't still have the screen open!
+ var/open_chamber = TRUE
+ ///Does it launch its grenades in a low arc or a high? Do they strike people in their path, or fly beyond?
+ var/is_lobbing = FALSE
+ ///Verboten munitions. This is a blacklist. Anything in this list isn't loadable.
+ var/disallowed_grenade_types = list(/obj/item/explosive/grenade/spawnergrenade, /obj/item/explosive/grenade/alien, /obj/item/explosive/grenade/incendiary/molotov, /obj/item/explosive/grenade/flashbang)
+ ///What is this weapon permitted to fire? This is a whitelist. Anything in this list can be fired. Anything.
+ var/valid_munitions = list(/obj/item/explosive/grenade)
+
+
+/obj/item/weapon/gun/launcher/grenade/set_gun_config_values()
+ ..()
+ recoil = RECOIL_AMOUNT_TIER_4 //Same as m37 shotgun.
+
+
+/obj/item/weapon/gun/launcher/grenade/on_pocket_insertion() //Plays load sfx whenever a nade is put into storage.
+ playsound(usr, reload_sound, 25, 1)
+ update_icon()
+
+/obj/item/weapon/gun/launcher/grenade/on_pocket_removal()
+ update_icon()
+
+/obj/item/weapon/gun/launcher/grenade/get_examine_text(mob/user) //Different treatment for single-shot VS multi-shot GLs.
+ . = ..()
+ if(get_dist(user, src) > 2 && user != loc)
+ return
+ if(length(cylinder.contents))
+ if(internal_slots == 1)
+ . += SPAN_NOTICE("It is loaded with a grenade.")
+ else
+ . += SPAN_NOTICE("It is loaded with [length(cylinder.contents)] / [internal_slots] grenades.")
+ else
+ . += SPAN_NOTICE("It is empty.")
+
+
+/obj/item/weapon/gun/launcher/grenade/update_icon()
+ ..()
+ var/GL_sprite = base_gun_icon
+ if(GL_has_empty_icon && cylinder && !length(cylinder.contents))
+ GL_sprite += "_e"
+ playsound(loc, cocked_sound, 25, 1)
+ if(GL_has_open_icon && open_chamber)
+ GL_sprite += "_o"
+ playsound(loc, cocked_sound, 25, 1)
+ icon_state = GL_sprite
+
+
+/obj/item/weapon/gun/launcher/grenade/attack_hand(mob/user)
+ if(!open_chamber || src != user.get_inactive_hand()) //Need to have the GL in your hands to open the cylinder.
+ return ..()
+ if(cylinder.handle_attack_hand(user))
+ ..()
+
+
+/obj/item/weapon/gun/launcher/grenade/unload(mob/user, reload_override = FALSE, drop_override = FALSE, loc_override = FALSE)
+ if(!open_chamber)
+ to_chat(user, SPAN_WARNING("[src] is closed!"))
+ return
+ if(!length(cylinder.contents))
+ to_chat(user, SPAN_WARNING("It's empty!"))
+ return
+
+ var/obj/item/explosive/grenade/nade = cylinder.contents[length(cylinder.contents)] //Grab the last-inserted one. Or the only one, as the case may be.
+ cylinder.remove_from_storage(nade, user.loc)
+
+ if(drop_override || !user)
+ nade.forceMove(get_turf(src))
+ else
+ user.put_in_hands(nade)
+
+ user.visible_message(SPAN_NOTICE("[user] unloads [nade] from [src]."),
+ SPAN_NOTICE("You unload [nade] from [src]."), null, 4, CHAT_TYPE_COMBAT_ACTION)
+ playsound(user, unload_sound, 30, 1)
+
+
+/obj/item/weapon/gun/launcher/grenade/attackby(obj/item/I, mob/user)
+ if(istype(I,/obj/item/attachable) && check_inactive_hand(user))
+ attach_to_gun(user,I)
+ return
+ return cylinder.attackby(I, user)
+
+/obj/item/weapon/gun/launcher/grenade/unique_action(mob/user)
+ if(isobserver(usr) || isxeno(usr))
+ return
+ if(locate(/datum/action/item_action/toggle_firing_level) in actions)
+ toggle_firing_level(usr)
+
+/obj/item/weapon/gun/launcher/grenade/proc/allowed_ammo_type(obj/item/I)
+ for(var/G in disallowed_grenade_types) //Check for the bad stuff.
+ if(istype(I, G))
+ return FALSE
+ for(var/G in valid_munitions) //Check if it has a ticket.
+ if(istype(I, G))
+ return TRUE
+
+
+/obj/item/weapon/gun/launcher/grenade/on_pocket_attackby(obj/item/explosive/grenade/I, mob/user) //the attack in question is on the internal container. Complete override - normal storage attackby cannot be silenced, and will always say "you put the x into y".
+ if(!open_chamber)
+ to_chat(user, SPAN_WARNING("[src] is closed!"))
+ return
+ if(!istype(I))
+ to_chat(user, SPAN_WARNING("You can't load [I] into [src]!"))
+ return
+ if(!allowed_ammo_type(I))
+ to_chat(user, SPAN_WARNING("[src] can't fire this type of grenade!"))
+ return
+ if(length(cylinder.contents) >= internal_slots)
+ to_chat(user, SPAN_WARNING("[src] cannot hold more grenades!"))
+ return
+ if(!cylinder.can_be_inserted(I, user)) //Technically includes whether there's room for it, but the above gives a tailored message.
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] loads [I] into [src]."),
+ SPAN_NOTICE("You load [I] into the grenade launcher."), null, 4, CHAT_TYPE_COMBAT_ACTION)
+ playsound(usr, reload_sound, 75, 1)
+ if(internal_slots > 1)
+ to_chat(user, SPAN_INFO("Now storing: [length(cylinder.contents) + 1] / [internal_slots] grenades."))
+
+ cylinder.handle_item_insertion(I, TRUE, user)
+
+
+/obj/item/weapon/gun/launcher/grenade/able_to_fire(mob/living/user) //Skillchecks and fire blockers go in the child items.
+ . = ..()
+ if(.)
+ if(!length(cylinder.contents))
+ to_chat(user, SPAN_WARNING("The [name] is empty."))
+ return FALSE
+ var/obj/item/explosive/grenade/G = cylinder.contents[1]
+ if(G.antigrief_protection && user.faction == FACTION_MARINE && explosive_antigrief_check(G, user))
+ to_chat(user, SPAN_WARNING("\The [name]'s safe-area accident inhibitor prevents you from firing!"))
+ msg_admin_niche("[key_name(user)] attempted to prime \a [G.name] in [get_area(src)] [ADMIN_JMP(src.loc)]")
+ return FALSE
+
+
+/obj/item/weapon/gun/launcher/grenade/afterattack(atom/target, mob/user, flag) //Not actually after the attack. After click, more like.
+ if(able_to_fire(user))
+ if(get_dist(target,user) <= 2)
+ var/obj/item/explosive/grenade/nade = cylinder.contents[1]
+ if(nade.dangerous)
+ to_chat(user, SPAN_WARNING("The grenade launcher beeps a warning noise. You are too close!"))
+ return
+ fire_grenade(target,user)
+
+
+/obj/item/weapon/gun/launcher/grenade/proc/fire_grenade(atom/target, mob/user)
+ set waitfor = 0
+ last_fired = world.time
+
+ var/to_firer = "You fire the [name]!"
+ if(internal_slots > 1)
+ to_firer += " [length(cylinder.contents)-1]/[internal_slots] grenades remaining."
+ user.visible_message(SPAN_DANGER("[user] fired a grenade!"),
+ SPAN_WARNING("[to_firer]"), message_flags = CHAT_TYPE_WEAPON_USE)
+ playsound(user.loc, fire_sound, 50, 1)
+
+ var/angle = round(Get_Angle(user,target))
+ muzzle_flash(angle,user)
+ simulate_recoil(0, user)
+
+ var/obj/item/explosive/grenade/fired = cylinder.contents[1]
+ cylinder.remove_from_storage(fired, user.loc)
+ var/pass_flags = NO_FLAGS
+ if(is_lobbing)
+ if(istype(fired, /obj/item/explosive/grenade/slug/baton))
+ if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ human_user.remember_dropped_object(fired)
+ fired.fingerprintslast = key_name(user)
+ pass_flags |= PASS_MOB_THRU_HUMAN|PASS_MOB_IS_OTHER|PASS_OVER
+ else
+ pass_flags |= PASS_MOB_THRU|PASS_HIGH_OVER
+
+ msg_admin_attack("[key_name_admin(user)] fired a grenade ([fired.name]) from \a ([name]).")
+ log_game("[key_name_admin(user)] used a grenade ([name]).")
+
+ fired.throw_range = 20
+ fired.det_time = min(10, fired.det_time)
+ fired.activate(user, FALSE)
+ fired.forceMove(get_turf(src))
+ fired.throw_atom(target, 20, SPEED_VERY_FAST, user, null, NORMAL_LAUNCH, pass_flags)
+
+
+
+//Doesn't use these. Listed for reference.
+/obj/item/weapon/gun/launcher/grenade/load_into_chamber()
+ return
+/obj/item/weapon/gun/launcher/grenade/reload_into_chamber()
+ return
+
+/obj/item/weapon/gun/launcher/grenade/has_ammunition()
+ return length(cylinder.contents)
+
+//-------------------------------------------------------
+//Toggle firing level special action for grenade launchers
+
+/datum/action/item_action/toggle_firing_level/New(Target, obj/item/holder)
+ . = ..()
+ name = "Toggle Firing Level"
+ button.name = name
+ update_icon()
+
+/datum/action/item_action/toggle_firing_level/action_activate()
+ var/obj/item/weapon/gun/launcher/grenade/G = holder_item
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ if(H.is_mob_incapacitated() || G.get_active_firearm(H, FALSE) != holder_item)
+ return
+ G.toggle_firing_level(usr)
+
+/datum/action/item_action/toggle_firing_level/proc/update_icon()
+ var/obj/item/weapon/gun/launcher/grenade/G = holder_item
+ if(G.is_lobbing)
+ action_icon_state = "hightoss_on"
+ else
+ action_icon_state = "hightoss_off"
+ button.overlays.Cut()
+ button.overlays += image('icons/mob/hud/actions.dmi', button, action_icon_state)
+
+/obj/item/weapon/gun/launcher/grenade/proc/toggle_firing_level(mob/user)
+ is_lobbing = !is_lobbing
+ to_chat(user, "[icon2html(src, usr)] You changed \the [src]'s firing level. You will now fire [is_lobbing ? "in an arcing path over obstacles" : "directly at your target"].")
+ playsound(loc,'sound/machines/click.ogg', 25, 1)
+ var/datum/action/item_action/toggle_firing_level/TFL = locate(/datum/action/item_action/toggle_firing_level) in actions
+ TFL.update_icon()
+
+//-------------------------------------------------------
+//M92 GRENADE LAUNCHER
+
+/obj/item/weapon/gun/launcher/grenade/m92
+ name = "\improper M92 grenade launcher"
+ desc = "A heavy, 6-shot grenade launcher used by the Colonial Marines for area denial and big explosions."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "m92"
+ item_state = "m92"
+ unacidable = TRUE
+ indestructible = 1
+ matter = list("metal" = 6000)
+ actions_types = list(/datum/action/item_action/toggle_firing_level)
+
+ attachable_allowed = list(/obj/item/attachable/magnetic_harness)
+ flags_item = TWOHANDED|NO_CRYO_STORE
+ map_specific_decoration = TRUE
+
+ is_lobbing = TRUE
+ internal_slots = 6
+ direct_draw = FALSE
+
+/obj/item/weapon/gun/launcher/grenade/m92/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 14, "rail_y" = 22, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
+
+/obj/item/weapon/gun/launcher/grenade/m92/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_4*4)
+
+/obj/item/weapon/gun/launcher/grenade/m92/able_to_fire(mob/living/user)
+ . = ..()
+ if (. && istype(user))
+ if(!skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_GRENADIER)
+ to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
+ return FALSE
+
+
+//-------------------------------------------------------
+//M81 GRENADE LAUNCHER
+
+/obj/item/weapon/gun/launcher/grenade/m81
+ name = "\improper M81 grenade launcher"
+ desc = "A lightweight, single-shot low-angle grenade launcher used by the Colonial Marines for area denial and big explosions."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/colony.dmi'
+ icon_state = "m81"
+ item_state = "m81" //needs a wield sprite.
+
+ matter = list("metal" = 7000)
+
+/obj/item/weapon/gun/launcher/grenade/m81/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 14, "rail_y" = 22, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
+
+/obj/item/weapon/gun/launcher/grenade/m81/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_4 * 1.5)
+
+/obj/item/weapon/gun/launcher/grenade/m81/on_pocket_removal()
+ ..()
+ playsound(usr, unload_sound, 30, 1)
+
+/obj/item/weapon/gun/launcher/grenade/m81/riot/able_to_fire(mob/living/user)
+ . = ..()
+ if (. && istype(user))
+ if(!skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED))
+ to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
+ return FALSE
+
+
+/obj/item/weapon/gun/launcher/grenade/m81/riot
+ name = "\improper M81 riot grenade launcher"
+ desc = "A lightweight, single-shot low-angle grenade launcher to launch tear gas grenades. Used by the Colonial Marines Military Police during riots."
+ valid_munitions = list(/obj/item/explosive/grenade/custom/teargas)
+ preload = /obj/item/explosive/grenade/custom/teargas
+
+//-------------------------------------------------------
+//M79 Grenade Launcher subtype of the M81
+
+/obj/item/weapon/gun/launcher/grenade/m81/m79//m79 variant for marines
+ name = "\improper M79 grenade launcher"
+ desc = "A heavy, low-angle 40mm grenade launcher. It's been in use since the Vietnam War, though this version has been modernized with an IFF enabled micro-computer. The wooden furniture is, in fact, made of painted hardened polykevlon."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "m79"
+ item_state = "m79"
+ flags_equip_slot = SLOT_BACK
+ preload = /obj/item/explosive/grenade/slug/baton
+ is_lobbing = TRUE
+ actions_types = list(/datum/action/item_action/toggle_firing_level)
+
+ fire_sound = 'sound/weapons/handling/m79_shoot.ogg'
+ cocked_sound = 'sound/weapons/handling/m79_break_open.ogg'
+ reload_sound = 'sound/weapons/handling/m79_reload.ogg'
+ unload_sound = 'sound/weapons/handling/m79_unload.ogg'
+
+ attachable_allowed = list(
+ /obj/item/attachable/magnetic_harness,
+ /obj/item/attachable/flashlight,
+ /obj/item/attachable/reddot,
+ /obj/item/attachable/reflex,
+ /obj/item/attachable/stock/m79,
+ )
+
+/obj/item/weapon/gun/launcher/grenade/m81/m79/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/stock/m79/S = new(src)
+ S.hidden = FALSE
+ S.flags_attach_features &= ~ATTACH_REMOVABLE
+ S.Attach(src)
+ update_attachable(S.slot)
+
+/obj/item/weapon/gun/launcher/grenade/m81/m79/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 9, "rail_y" = 22, "under_x" = 19, "under_y" = 14, "stock_x" = 14, "stock_y" = 14)
+
+/obj/item/weapon/gun/launcher/grenade/m81/m79/set_bullet_traits()
+ LAZYADD(traits_to_give, list(
+ BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)//might not need this because of is_lobbing, but let's keep it just incase
+ ))
diff --git a/code/modules/projectiles/guns/specialist/launcher/launcher.dm b/code/modules/projectiles/guns/specialist/launcher/launcher.dm
new file mode 100644
index 000000000000..70f00aa83c35
--- /dev/null
+++ b/code/modules/projectiles/guns/specialist/launcher/launcher.dm
@@ -0,0 +1,46 @@
+//-------------------------------------------------------
+//HEAVY WEAPONS
+
+/obj/item/weapon/gun/launcher
+ gun_category = GUN_CATEGORY_HEAVY
+ has_empty_icon = FALSE
+ has_open_icon = FALSE
+ ///gun update_icon doesn't detect that guns with no magazine are loaded or not, and will always append _o or _e if possible.
+ var/GL_has_empty_icon = TRUE
+ ///gun update_icon doesn't detect that guns with no magazine are loaded or not, and will always append _o or _e if possible.
+ var/GL_has_open_icon = FALSE
+
+ ///Internal storage item used as magazine. Must be initialised to work! Set parameters by variables or it will inherit standard numbers from storage.dm. Got to call it *something* and 'magazine' or w/e would be confusing.
+ var/obj/item/storage/internal/cylinder
+ /// Variable that initializes the above.
+ var/has_cylinder = FALSE
+ ///What single item to fill the storage with, if any. This does not respect w_class.
+ var/preload
+ ///How many items can be inserted. "Null" = backpack-style size-based inventory. You'll have to set max_storage_space too if you do that, and arrange any initial contents. Iff you arrange to put in more items than the storage can hold, they can be taken out but not replaced.
+ var/internal_slots
+ ///how big an item can be inserted.
+ var/internal_max_w_class
+ ///the sfx played when the storage is opened.
+ var/use_sound = null
+ ///Whether clicking a held weapon with an empty hand will open its inventory or draw a munition out.
+ var/direct_draw = TRUE
+
+/obj/item/weapon/gun/launcher/Initialize(mapload, spawn_empty) //If changing vars on init, be sure to do the parent proccall *after* the change.
+ . = ..()
+ if(has_cylinder)
+ cylinder = new /obj/item/storage/internal(src)
+ cylinder.storage_slots = internal_slots
+ cylinder.max_w_class = internal_max_w_class
+ cylinder.use_sound = use_sound
+ if(direct_draw)
+ cylinder.storage_flags ^= STORAGE_USING_DRAWING_METHOD
+ if(preload && !spawn_empty) for(var/i = 1 to cylinder.storage_slots)
+ new preload(cylinder)
+ update_icon()
+
+/obj/item/weapon/gun/launcher/verb/toggle_draw_mode()
+ set name = "Switch Storage Drawing Method"
+ set category = "Object"
+ set src in usr
+
+ cylinder.storage_draw_logic(src.name)
diff --git a/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm b/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm
new file mode 100644
index 000000000000..6d998002134c
--- /dev/null
+++ b/code/modules/projectiles/guns/specialist/launcher/rocket_launcher.dm
@@ -0,0 +1,369 @@
+
+//-------------------------------------------------------
+//M5 RPG
+
+/obj/item/weapon/gun/launcher/rocket
+ name = "\improper M5 RPG"
+ desc = "The M5 RPG is the primary anti-armor weapon of the USCM. Used to take out light-tanks and enemy structures, the M5 RPG is a dangerous weapon with a variety of combat uses."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "m5"
+ item_state = "m5"
+ unacidable = TRUE
+ indestructible = 1
+
+ matter = list("metal" = 10000)
+ current_mag = /obj/item/ammo_magazine/rocket
+ flags_equip_slot = NO_FLAGS
+ w_class = SIZE_HUGE
+ force = 15
+ wield_delay = WIELD_DELAY_HORRIBLE
+ delay_style = WEAPON_DELAY_NO_FIRE
+ aim_slowdown = SLOWDOWN_ADS_SPECIALIST
+ attachable_allowed = list(
+ /obj/item/attachable/magnetic_harness,
+ )
+
+ flags_gun_features = GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_INTERNAL_MAG
+ var/datum/effect_system/smoke_spread/smoke
+
+ flags_item = TWOHANDED|NO_CRYO_STORE
+ var/skill_locked = TRUE
+
+/obj/item/weapon/gun/launcher/rocket/Initialize(mapload, spawn_empty)
+ . = ..()
+ smoke = new()
+ smoke.attach(src)
+
+/obj/item/weapon/gun/launcher/rocket/Destroy()
+ QDEL_NULL(smoke)
+ return ..()
+
+
+/obj/item/weapon/gun/launcher/rocket/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 6, "rail_y" = 19, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
+
+
+/obj/item/weapon/gun/launcher/rocket/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_6*2)
+ accuracy_mult = BASE_ACCURACY_MULT
+ scatter = SCATTER_AMOUNT_TIER_6
+ damage_mult = BASE_BULLET_DAMAGE_MULT
+ recoil = RECOIL_AMOUNT_TIER_3
+
+
+/obj/item/weapon/gun/launcher/rocket/get_examine_text(mob/user)
+ . = ..()
+ if(current_mag.current_rounds <= 0)
+ . += "It's not loaded."
+ return
+ if(current_mag.current_rounds > 0)
+ . += "It has an 84mm [ammo.name] loaded."
+
+
+/obj/item/weapon/gun/launcher/rocket/able_to_fire(mob/living/user)
+ . = ..()
+ if (. && istype(user)) //Let's check all that other stuff first.
+ if(skill_locked && !skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_ROCKET)
+ to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
+ return 0
+ if(user.faction == FACTION_MARINE && explosive_antigrief_check(src, user))
+ to_chat(user, SPAN_WARNING("\The [name]'s safe-area accident inhibitor prevents you from firing!"))
+ msg_admin_niche("[key_name(user)] attempted to fire \a [name] in [get_area(src)] [ADMIN_JMP(loc)]")
+ return FALSE
+ if(current_mag && current_mag.current_rounds > 0)
+ make_rocket(user, 0, 1)
+
+/obj/item/weapon/gun/launcher/rocket/load_into_chamber(mob/user)
+// if(active_attachable) active_attachable = null
+ return ready_in_chamber()
+
+//No such thing
+/obj/item/weapon/gun/launcher/rocket/reload_into_chamber(mob/user)
+ return TRUE
+
+/obj/item/weapon/gun/launcher/rocket/delete_bullet(obj/projectile/projectile_to_fire, refund = 0)
+ if(!current_mag)
+ return
+ qdel(projectile_to_fire)
+ if(refund)
+ current_mag.current_rounds++
+ return TRUE
+
+/obj/item/weapon/gun/launcher/rocket/proc/make_rocket(mob/user, drop_override = 0, empty = 1)
+ if(!current_mag)
+ return
+
+ var/obj/item/ammo_magazine/rocket/r = new current_mag.type()
+ //if there's ever another type of custom rocket ammo this logic should just be moved into a function on the rocket
+ if(istype(current_mag, /obj/item/ammo_magazine/rocket/custom) && !empty)
+ //set the custom rocket variables here.
+ var/obj/item/ammo_magazine/rocket/custom/k = new /obj/item/ammo_magazine/rocket/custom
+ var/obj/item/ammo_magazine/rocket/custom/cur_mag_cast = current_mag
+ k.contents = cur_mag_cast.contents
+ k.desc = cur_mag_cast.desc
+ k.fuel = cur_mag_cast.fuel
+ k.icon_state = cur_mag_cast.icon_state
+ k.warhead = cur_mag_cast.warhead
+ k.locked = cur_mag_cast.locked
+ k.name = cur_mag_cast.name
+ k.filters = cur_mag_cast.filters
+ r = k
+
+ if(empty)
+ r.current_rounds = 0
+ if(drop_override || !user) //If we want to drop it on the ground or there's no user.
+ r.forceMove(get_turf(src)) //Drop it on the ground.
+ else
+ user.put_in_hands(r)
+ r.update_icon()
+
+/obj/item/weapon/gun/launcher/rocket/reload(mob/user, obj/item/ammo_magazine/rocket)
+ if(!current_mag)
+ return
+ if(flags_gun_features & GUN_BURST_FIRING)
+ return
+
+ if(!rocket || !istype(rocket) || !istype(src, rocket.gun_type))
+ to_chat(user, SPAN_WARNING("That's not going to fit!"))
+ return
+
+ if(current_mag.current_rounds > 0)
+ to_chat(user, SPAN_WARNING("[src] is already loaded!"))
+ return
+
+ if(rocket.current_rounds <= 0)
+ to_chat(user, SPAN_WARNING("That frame is empty!"))
+ return
+
+ if(user)
+ to_chat(user, SPAN_NOTICE("You begin reloading [src]. Hold still..."))
+ if(do_after(user,current_mag.reload_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
+ qdel(current_mag)
+ user.drop_inv_item_on_ground(rocket)
+ current_mag = rocket
+ rocket.forceMove(src)
+ replace_ammo(,rocket)
+ to_chat(user, SPAN_NOTICE("You load [rocket] into [src]."))
+ if(reload_sound)
+ playsound(user, reload_sound, 25, 1)
+ else
+ playsound(user,'sound/machines/click.ogg', 25, 1)
+ else
+ to_chat(user, SPAN_WARNING("Your reload was interrupted!"))
+ return
+ else
+ qdel(current_mag)
+ current_mag = rocket
+ rocket.forceMove(src)
+ replace_ammo(,rocket)
+ return TRUE
+
+/obj/item/weapon/gun/launcher/rocket/unload(mob/user, reload_override = 0, drop_override = 0)
+ if(user && current_mag)
+ if(current_mag.current_rounds <= 0)
+ to_chat(user, SPAN_WARNING("[src] is already empty!"))
+ return
+ to_chat(user, SPAN_NOTICE("You begin unloading [src]. Hold still..."))
+ if(do_after(user,current_mag.reload_delay, INTERRUPT_ALL, BUSY_ICON_FRIENDLY))
+ if(current_mag.current_rounds <= 0)
+ to_chat(user, SPAN_WARNING("You have already unloaded \the [src]."))
+ return
+ playsound(user, unload_sound, 25, 1)
+ user.visible_message(SPAN_NOTICE("[user] unloads [ammo] from [src]."),
+ SPAN_NOTICE("You unload [ammo] from [src]."))
+ make_rocket(user, drop_override, 0)
+ current_mag.current_rounds = 0
+
+//Adding in the rocket backblast. The tile behind the specialist gets blasted hard enough to down and slightly wound anyone
+/obj/item/weapon/gun/launcher/rocket/apply_bullet_effects(obj/projectile/projectile_to_fire, mob/user, i = 1, reflex = 0)
+ . = ..()
+ if(!HAS_TRAIT(user, TRAIT_EAR_PROTECTION) && ishuman(user))
+ var/mob/living/carbon/human/huser = user
+ to_chat(user, SPAN_WARNING("Augh!! \The [src]'s launch blast resonates extremely loudly in your ears! You probably should have worn some sort of ear protection..."))
+ huser.apply_effect(6, STUTTER)
+ huser.emote("pain")
+ huser.SetEarDeafness(max(user.ear_deaf,10))
+
+ var/backblast_loc = get_turf(get_step(user.loc, turn(user.dir, 180)))
+ smoke.set_up(1, 0, backblast_loc, turn(user.dir, 180))
+ smoke.start()
+ playsound(src, 'sound/weapons/gun_rocketlauncher.ogg', 100, TRUE, 10)
+ for(var/mob/living/carbon/C in backblast_loc)
+ if(!C.lying && !HAS_TRAIT(C, TRAIT_EAR_PROTECTION)) //Have to be standing up to get the fun stuff
+ C.apply_damage(15, BRUTE) //The shockwave hurts, quite a bit. It can knock unarmored targets unconscious in real life
+ C.apply_effect(4, STUN) //For good measure
+ C.apply_effect(6, STUTTER)
+ C.emote("pain")
+
+//-------------------------------------------------------
+//M5 RPG'S MEAN FUCKING COUSIN
+
+/obj/item/weapon/gun/launcher/rocket/m57a4
+ name = "\improper M57-A4 'Lightning Bolt' quad thermobaric launcher"
+ desc = "The M57-A4 'Lightning Bolt' is possibly the most destructive man-portable weapon ever made. It is a 4-barreled missile launcher capable of burst-firing 4 thermobaric missiles. Enough said."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/event.dmi'
+ icon_state = "m57a4"
+ item_state = "m57a4"
+
+ current_mag = /obj/item/ammo_magazine/rocket/m57a4
+ aim_slowdown = SLOWDOWN_ADS_SUPERWEAPON
+ flags_gun_features = GUN_WIELDED_FIRING_ONLY
+
+/obj/item/weapon/gun/launcher/rocket/m57a4/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_5)
+ set_burst_delay(FIRE_DELAY_TIER_7)
+ set_burst_amount(BURST_AMOUNT_TIER_4)
+ accuracy_mult = BASE_ACCURACY_MULT - HIT_ACCURACY_MULT_TIER_4
+ scatter = SCATTER_AMOUNT_TIER_6
+ damage_mult = BASE_BULLET_DAMAGE_MULT
+ recoil = RECOIL_AMOUNT_TIER_3
+
+
+//-------------------------------------------------------
+//AT rocket launchers, can be used by non specs
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank //reloadable
+ name = "\improper QH-4 Shoulder-Mounted Anti-Tank RPG"
+ desc = "Used to take out light-tanks and enemy structures, the QH-4 is a dangerous weapon specialised against vehicles. Requires direct hits to penetrate vehicle armor."
+ icon_state = "m83a2"
+ item_state = "m83a2"
+ unacidable = FALSE
+ indestructible = FALSE
+ skill_locked = FALSE
+
+ current_mag = /obj/item/ammo_magazine/rocket/anti_tank
+
+ attachable_allowed = list()
+
+ flags_gun_features = GUN_WIELDED_FIRING_ONLY
+
+ flags_item = TWOHANDED
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/set_bullet_traits()
+ . = ..()
+ LAZYADD(traits_to_give, list(
+ BULLET_TRAIT_ENTRY_ID("vehicles", /datum/element/bullet_trait_damage_boost, 20, GLOB.damage_boost_vehicles),
+ ))
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable //single shot and disposable
+ name = "\improper M83A2 SADAR"
+ desc = "The M83A2 SADAR is a lightweight one-shot anti-armor weapon capable of engaging enemy vehicles at ranges up to 1,000m. Fully disposable, the rocket's launcher is discarded after firing. When stowed (unique-action), the SADAR system consists of a watertight carbon-fiber composite blast tube, inside of which is an aluminum launch tube containing the missile. The weapon is fired by pushing a charge button on the trigger grip. It is sighted and fired from the shoulder."
+ var/fired = FALSE
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/get_examine_text(mob/user)
+ . = ..()
+ . += SPAN_NOTICE("You can fold it up with unique-action.")
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/Fire(atom/target, mob/living/user, params, reflex, dual_wield)
+ . = ..()
+ if(.)
+ fired = TRUE
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/unique_action(mob/M)
+ if(fired)
+ to_chat(M, SPAN_WARNING("\The [src] has already been fired - you can't fold it back up again!"))
+ return
+
+ M.visible_message(SPAN_NOTICE("[M] begins to fold up \the [src]."), SPAN_NOTICE("You start to fold and collapse closed \the [src]."))
+
+ if(!do_after(M, 2 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC))
+ to_chat(M, SPAN_NOTICE("You stop folding up \the [src]"))
+ return
+
+ fold(M)
+ M.visible_message(SPAN_NOTICE("[M] finishes folding \the [src]."), SPAN_NOTICE("You finish folding \the [src]."))
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/proc/fold(mob/user)
+ var/obj/item/prop/folded_anti_tank_sadar/F = new /obj/item/prop/folded_anti_tank_sadar(src.loc)
+ transfer_label_component(F)
+ qdel(src)
+ user.put_in_active_hand(F)
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/reload()
+ to_chat(usr, SPAN_WARNING("You cannot reload \the [src]!"))
+ return
+
+/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/unload()
+ to_chat(usr, SPAN_WARNING("You cannot unload \the [src]!"))
+ return
+
+//folded version of the sadar
+/obj/item/prop/folded_anti_tank_sadar
+ name = "\improper M83 SADAR (folded)"
+ desc = "An M83 SADAR Anti-Tank RPG, compacted for easier storage. Can be unfolded with the Z key."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "m83a2_folded"
+ w_class = SIZE_MEDIUM
+ garbage = FALSE
+
+/obj/item/prop/folded_anti_tank_sadar/attack_self(mob/user)
+ user.visible_message(SPAN_NOTICE("[user] begins to unfold \the [src]."), SPAN_NOTICE("You start to unfold and expand \the [src]."))
+ playsound(src, 'sound/items/component_pickup.ogg', 20, TRUE, 5)
+
+ if(!do_after(user, 4 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC))
+ to_chat(user, SPAN_NOTICE("You stop unfolding \the [src]"))
+ return
+
+ unfold(user)
+
+ user.visible_message(SPAN_NOTICE("[user] finishes unfolding \the [src]."), SPAN_NOTICE("You finish unfolding \the [src]."))
+ playsound(src, 'sound/items/component_pickup.ogg', 20, TRUE, 5)
+ . = ..()
+
+/obj/item/prop/folded_anti_tank_sadar/proc/unfold(mob/user)
+ var/obj/item/weapon/gun/launcher/rocket/anti_tank/disposable/F = new /obj/item/weapon/gun/launcher/rocket/anti_tank/disposable(src.loc)
+ transfer_label_component(F)
+ qdel(src)
+ user.put_in_active_hand(F)
+
+//-------------------------------------------------------
+//UPP Rocket Launcher
+
+/obj/item/weapon/gun/launcher/rocket/upp
+ name = "\improper HJRA-12 Handheld Anti-Tank Grenade Launcher"
+ desc = "The HJRA-12 Handheld Anti-Tank Grenade Launcher is the standard Anti-Armor weapon of the UPP. It is designed to be easy to use and to take out or disable armored vehicles."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/upp.dmi'
+ icon_state = "hjra12"
+ item_state = "hjra12"
+ skill_locked = FALSE
+ current_mag = /obj/item/ammo_magazine/rocket/upp/at
+
+ attachable_allowed = list(/obj/item/attachable/upp_rpg_breech)
+
+ flags_gun_features = GUN_WIELDED_FIRING_ONLY
+
+ flags_item = TWOHANDED
+
+/obj/item/weapon/gun/launcher/rocket/upp/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 33, "muzzle_y" = 18,"rail_x" = 6, "rail_y" = 19, "under_x" = 19, "under_y" = 14, "stock_x" = -6, "stock_y" = 16, "special_x" = 37, "special_y" = 16)
+
+/obj/item/weapon/gun/launcher/rocket/upp/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/upp_rpg_breech/S = new(src)
+ S.flags_attach_features &= ~ATTACH_REMOVABLE
+ S.Attach(src)
+ update_attachables()
+
+ var/obj/item/attachable/magnetic_harness/Integrated = new(src)
+ Integrated.hidden = TRUE
+ Integrated.flags_attach_features &= ~ATTACH_REMOVABLE
+ Integrated.Attach(src)
+ update_attachable(Integrated.slot)
+
+/obj/item/weapon/gun/launcher/rocket/upp/apply_bullet_effects(obj/projectile/projectile_to_fire, mob/user, i = 1, reflex = 0)
+ . = ..()
+ if(!HAS_TRAIT(user, TRAIT_EAR_PROTECTION) && ishuman(user))
+ return
+
+ var/backblast_loc = get_turf(get_step(user.loc, turn(user.dir, 180)))
+ smoke.set_up(1, 0, backblast_loc, turn(user.dir, 180))
+ smoke.start()
+ playsound(src, 'sound/weapons/gun_rocketlauncher.ogg', 100, TRUE, 10)
+ for(var/mob/living/carbon/C in backblast_loc)
+ if(!C.lying && !HAS_TRAIT(C, TRAIT_EAR_PROTECTION)) //Have to be standing up to get the fun stuff
+ C.apply_damage(15, BRUTE) //The shockwave hurts, quite a bit. It can knock unarmored targets unconscious in real life
+ C.apply_effect(4, STUN) //For good measure
+ C.apply_effect(6, STUTTER)
+ C.emote("pain")
diff --git a/code/modules/projectiles/guns/specialist/scout.dm b/code/modules/projectiles/guns/specialist/scout.dm
new file mode 100644
index 000000000000..c2c5abd54add
--- /dev/null
+++ b/code/modules/projectiles/guns/specialist/scout.dm
@@ -0,0 +1,83 @@
+//M4RA custom marksman rifle
+
+/obj/item/weapon/gun/rifle/m4ra_custom
+ name = "\improper M4RA custom battle rifle"
+ desc = "This is a further improvement upon the already rock-solid M4RA. Made by the USCM armorers on Chinook station - This variant of the M4RA has a specifically milled magazine well to accept A19 rounds. It sports a light-weight titantium-alloy frame, better responsive to the heavy kick of the tailor-made A19 rounds."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "m4ra_custom"
+ item_state = "m4ra_custom"
+ unacidable = TRUE
+ indestructible = 1
+ accepted_ammo = list(
+ /obj/item/ammo_magazine/rifle/m4ra,
+ /obj/item/ammo_magazine/rifle/m4ra/ap,
+ /obj/item/ammo_magazine/rifle/m4ra/ext,
+ /obj/item/ammo_magazine/rifle/m4ra/rubber,
+ /obj/item/ammo_magazine/rifle/m4ra/incendiary,
+ /obj/item/ammo_magazine/rifle/m4ra/heap,
+ /obj/item/ammo_magazine/rifle/m4ra/penetrating,
+ /obj/item/ammo_magazine/rifle/m4ra/custom,
+ /obj/item/ammo_magazine/rifle/m4ra/custom/incendiary,
+ /obj/item/ammo_magazine/rifle/m4ra/custom/impact,
+
+ )
+
+ fire_sound = 'sound/weapons/gun_m4ra.ogg'
+ reload_sound = 'sound/weapons/handling/l42_reload.ogg'
+ unload_sound = 'sound/weapons/handling/l42_unload.ogg'
+ current_mag = /obj/item/ammo_magazine/rifle/m4ra/custom
+ force = 26
+ attachable_allowed = list(
+ /obj/item/attachable/suppressor,
+ /obj/item/attachable/bayonet,
+ /obj/item/attachable/bayonet/upp,
+ /obj/item/attachable/bayonet/co2,
+ /obj/item/attachable/reddot,
+ /obj/item/attachable/reflex,
+ /obj/item/attachable/flashlight,
+ /obj/item/attachable/extended_barrel,
+ /obj/item/attachable/magnetic_harness,
+ /obj/item/attachable/bipod,
+ /obj/item/attachable/attached_gun/shotgun,
+ /obj/item/attachable/verticalgrip,
+ /obj/item/attachable/angledgrip,
+ /obj/item/attachable/lasersight,
+ /obj/item/attachable/scope,
+ /obj/item/attachable/scope/mini,
+ /obj/item/attachable/flashlight/grip,
+ )
+
+ flags_gun_features = GUN_AUTO_EJECTOR|GUN_SPECIALIST|GUN_CAN_POINTBLANK|GUN_AMMO_COUNTER
+ map_specific_decoration = TRUE
+ aim_slowdown = SLOWDOWN_ADS_QUICK
+ flags_item = TWOHANDED|NO_CRYO_STORE
+
+/obj/item/weapon/gun/rifle/m4ra_custom/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/m4ra_barrel_custom/integrated = new(src)
+ integrated.flags_attach_features &= ~ATTACH_REMOVABLE
+ integrated.Attach(src)
+ update_attachable(integrated.slot)
+
+
+/obj/item/weapon/gun/rifle/m4ra_custom/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 43, "muzzle_y" = 17,"rail_x" = 23, "rail_y" = 21, "under_x" = 30, "under_y" = 11, "stock_x" = 24, "stock_y" = 13, "special_x" = 37, "special_y" = 16)
+
+/obj/item/weapon/gun/rifle/m4ra_custom/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_6)
+ set_burst_amount(BURST_AMOUNT_TIER_2)
+ set_burst_delay(FIRE_DELAY_TIER_12)
+ accuracy_mult = BASE_ACCURACY_MULT + HIT_ACCURACY_MULT_TIER_2
+ scatter = SCATTER_AMOUNT_TIER_8
+ burst_scatter_mult = SCATTER_AMOUNT_TIER_8
+ damage_mult = BASE_BULLET_DAMAGE_MULT + BULLET_DAMAGE_MULT_TIER_2
+ recoil = RECOIL_AMOUNT_TIER_5
+ damage_falloff_mult = 0
+
+/obj/item/weapon/gun/rifle/m4ra_custom/able_to_fire(mob/living/user)
+ . = ..()
+ if (. && istype(user)) //Let's check all that other stuff first.
+ if(!skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_SCOUT)
+ to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
+ return FALSE
diff --git a/code/modules/projectiles/guns/specialist/sniper.dm b/code/modules/projectiles/guns/specialist/sniper.dm
new file mode 100644
index 000000000000..17a2c0f26887
--- /dev/null
+++ b/code/modules/projectiles/guns/specialist/sniper.dm
@@ -0,0 +1,510 @@
+//-------------------------------------------------------
+//SNIPER RIFLES
+//Keyword rifles. They are subtype of rifles, but still contained here as a specialist weapon.
+
+//Because this parent type did not exist
+//Note that this means that snipers will have a slowdown of 3, due to the scope
+/obj/item/weapon/gun/rifle/sniper
+ aim_slowdown = SLOWDOWN_ADS_SPECIALIST
+ wield_delay = WIELD_DELAY_SLOW
+
+ var/has_aimed_shot = TRUE
+ var/aiming_time = 1.25 SECONDS
+ var/aimed_shot_cooldown
+ var/aimed_shot_cooldown_delay = 2.5 SECONDS
+
+ var/enable_aimed_shot_laser = TRUE
+ var/sniper_lockon_icon = "sniper_lockon"
+ var/obj/effect/ebeam/sniper_beam_type = /obj/effect/ebeam/laser
+ var/sniper_beam_icon = "laser_beam"
+ var/skill_locked = TRUE
+
+/obj/item/weapon/gun/rifle/sniper/get_examine_text(mob/user)
+ . = ..()
+ if(!has_aimed_shot)
+ return
+ . += SPAN_NOTICE("This weapon has an unique ability, Aimed Shot, allowing it to deal great damage after a windup.
Additionally, the aimed shot can be sped up with a tracking laser, which is enabled by default but may be disabled.")
+
+/obj/item/weapon/gun/rifle/sniper/Initialize(mapload, spawn_empty)
+ if(has_aimed_shot)
+ LAZYADD(actions_types, list(/datum/action/item_action/specialist/aimed_shot, /datum/action/item_action/specialist/toggle_laser))
+ return ..()
+
+/obj/item/weapon/gun/rifle/sniper/able_to_fire(mob/living/user)
+ . = ..()
+ if(. && istype(user) && skill_locked) //Let's check all that other stuff first.
+ if(!skillcheck(user, SKILL_SPEC_WEAPONS, SKILL_SPEC_ALL) && user.skills.get_skill_level(SKILL_SPEC_WEAPONS) != SKILL_SPEC_SNIPER)
+ to_chat(user, SPAN_WARNING("You don't seem to know how to use \the [src]..."))
+ return 0
+
+// Aimed shot ability
+/datum/action/item_action/specialist/aimed_shot
+ ability_primacy = SPEC_PRIMARY_ACTION_2
+ var/minimum_aim_distance = 2
+
+/datum/action/item_action/specialist/aimed_shot/New(mob/living/user, obj/item/holder)
+ ..()
+ name = "Aimed Shot"
+ button.name = name
+ button.overlays.Cut()
+ var/image/IMG = image('icons/mob/hud/actions.dmi', button, "sniper_aim")
+ button.overlays += IMG
+ var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
+ sniper_rifle.aimed_shot_cooldown = world.time
+
+/*
+ ACTIONS SPECIALSIT SNIPER CAN TAKE
+*/
+/datum/action/item_action/specialist/aimed_shot/action_activate()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ if(H.selected_ability == src)
+ to_chat(H, "You will no longer use [name] with \
+ [H.client && H.client.prefs && H.client.prefs.toggle_prefs & TOGGLE_MIDDLE_MOUSE_CLICK ? "middle-click" : "shift-click"].")
+ button.icon_state = "template"
+ H.selected_ability = null
+ else
+ to_chat(H, "You will now use [name] with \
+ [H.client && H.client.prefs && H.client.prefs.toggle_prefs & TOGGLE_MIDDLE_MOUSE_CLICK ? "middle-click" : "shift-click"].")
+ if(H.selected_ability)
+ H.selected_ability.button.icon_state = "template"
+ H.selected_ability = null
+ button.icon_state = "template_on"
+ H.selected_ability = src
+
+/datum/action/item_action/specialist/aimed_shot/can_use_action()
+ var/mob/living/carbon/human/H = owner
+ if(istype(H) && !H.is_mob_incapacitated() && !H.lying && (holder_item == H.r_hand || holder_item || H.l_hand))
+ return TRUE
+
+/datum/action/item_action/specialist/aimed_shot/proc/use_ability(atom/A)
+ var/mob/living/carbon/human/human = owner
+ if(!istype(A, /mob/living))
+ return
+
+ var/mob/living/target = A
+
+ if(target.stat == DEAD || target == human)
+ return
+
+ var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
+ if(world.time < sniper_rifle.aimed_shot_cooldown)
+ return
+
+ if(!check_can_use(target))
+ return
+
+ human.face_atom(target)
+
+ ///Add a decisecond to the default 1.5 seconds for each two tiles to hit.
+ var/distance = round(get_dist(target, human) * 0.5)
+ var/f_aiming_time = sniper_rifle.aiming_time + distance
+
+ var/aim_multiplier = 1
+ var/aiming_buffs
+
+ if(sniper_rifle.enable_aimed_shot_laser)
+ aim_multiplier = 0.6
+ aiming_buffs++
+
+ if(HAS_TRAIT(target, TRAIT_SPOTTER_LAZED))
+ aim_multiplier = 0.5
+ aiming_buffs++
+
+ if(aiming_buffs > 1)
+ aim_multiplier = 0.35
+
+ f_aiming_time *= aim_multiplier
+
+ var/image/lockon_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = sniper_rifle.sniper_lockon_icon)
+
+ var/x_offset = -target.pixel_x + target.base_pixel_x
+ var/y_offset = (target.icon_size - world.icon_size) * 0.5 - target.pixel_y + target.base_pixel_y
+
+ lockon_icon.pixel_x = x_offset
+ lockon_icon.pixel_y = y_offset
+ target.overlays += lockon_icon
+
+ var/image/lockon_direction_icon
+ if(!sniper_rifle.enable_aimed_shot_laser)
+ lockon_direction_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "[sniper_rifle.sniper_lockon_icon]_direction", dir = get_cardinal_dir(target, human))
+ lockon_direction_icon.pixel_x = x_offset
+ lockon_direction_icon.pixel_y = y_offset
+ target.overlays += lockon_direction_icon
+ if(human.client)
+ playsound_client(human.client, 'sound/weapons/TargetOn.ogg', human, 50)
+ playsound(target, 'sound/weapons/TargetOn.ogg', 70, FALSE, 8, falloff = 0.4)
+
+ var/datum/beam/laser_beam
+ if(sniper_rifle.enable_aimed_shot_laser)
+ laser_beam = target.beam(human, sniper_rifle.sniper_beam_icon, 'icons/effects/beam.dmi', (f_aiming_time + 1 SECONDS), beam_type = sniper_rifle.sniper_beam_type)
+ laser_beam.visuals.alpha = 0
+ animate(laser_beam.visuals, alpha = initial(laser_beam.visuals.alpha), f_aiming_time, easing = SINE_EASING|EASE_OUT)
+
+ ////timer is (f_spotting_time + 1 SECONDS) because sometimes it janks out before the doafter is done. blame sleeps or something
+
+ if(!do_after(human, f_aiming_time, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, NO_BUSY_ICON))
+ target.overlays -= lockon_icon
+ target.overlays -= lockon_direction_icon
+ qdel(laser_beam)
+ return
+
+ target.overlays -= lockon_icon
+ target.overlays -= lockon_direction_icon
+ qdel(laser_beam)
+
+ if(!check_can_use(target, TRUE))
+ return
+
+ var/obj/projectile/aimed_proj = sniper_rifle.in_chamber
+ aimed_proj.projectile_flags |= PROJECTILE_BULLSEYE
+ aimed_proj.AddComponent(/datum/component/homing_projectile, target, human)
+ sniper_rifle.Fire(target, human)
+
+/datum/action/item_action/specialist/aimed_shot/proc/check_can_use(mob/M, cover_lose_focus)
+ var/mob/living/carbon/human/H = owner
+ var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
+
+ if(!can_use_action())
+ return FALSE
+
+ if(sniper_rifle != H.r_hand && sniper_rifle != H.l_hand)
+ to_chat(H, SPAN_WARNING("How do you expect to do this without your sniper rifle?"))
+ return FALSE
+
+ if(!(sniper_rifle.flags_item & WIELDED))
+ to_chat(H, SPAN_WARNING("Your aim is not stable enough with one hand. Use both hands!"))
+ return FALSE
+
+ if(!sniper_rifle.in_chamber)
+ to_chat(H, SPAN_WARNING("\The [sniper_rifle] is unloaded!"))
+ return FALSE
+
+ if(get_dist(H, M) < minimum_aim_distance)
+ to_chat(H, SPAN_WARNING("\The [M] is too close to get a proper shot!"))
+ return FALSE
+
+ var/obj/projectile/P = sniper_rifle.in_chamber
+ // TODO: Make the below logic only occur in certain circumstances. Check goggles, maybe? -Kaga
+ if(check_shot_is_blocked(H, M, P))
+ to_chat(H, SPAN_WARNING("Something is in the way, or you're out of range!"))
+ if(cover_lose_focus)
+ to_chat(H, SPAN_WARNING("You lose focus."))
+ COOLDOWN_START(sniper_rifle, aimed_shot_cooldown, sniper_rifle.aimed_shot_cooldown_delay * 0.5)
+ return FALSE
+
+ COOLDOWN_START(sniper_rifle, aimed_shot_cooldown, sniper_rifle.aimed_shot_cooldown_delay)
+ return TRUE
+
+/datum/action/item_action/specialist/aimed_shot/proc/check_shot_is_blocked(mob/firer, mob/target, obj/projectile/P)
+ var/list/turf/path = getline2(firer, target, include_from_atom = FALSE)
+ if(!path.len || get_dist(firer, target) > P.ammo.max_range)
+ return TRUE
+
+ var/blocked = FALSE
+ for(var/turf/T in path)
+ if(T.density || T.opacity)
+ blocked = TRUE
+ break
+
+ for(var/obj/O in T)
+ if(O.get_projectile_hit_boolean(P))
+ blocked = TRUE
+ break
+
+ for(var/obj/effect/particle_effect/smoke/S in T)
+ blocked = TRUE
+ break
+
+ return blocked
+
+// Snipers may enable or disable their laser tracker at will.
+/datum/action/item_action/specialist/toggle_laser
+
+/datum/action/item_action/specialist/toggle_laser/New(mob/living/user, obj/item/holder)
+ ..()
+ name = "Toggle Tracker Laser"
+ button.name = name
+ button.overlays.Cut()
+ var/image/IMG = image('icons/mob/hud/actions.dmi', button, "sniper_toggle_laser_on")
+ button.overlays += IMG
+ update_button_icon()
+
+/datum/action/item_action/specialist/toggle_laser/update_button_icon()
+ var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
+
+ var/icon = 'icons/mob/hud/actions.dmi'
+ var/icon_state = "sniper_toggle_laser_[sniper_rifle.enable_aimed_shot_laser ? "on" : "off"]"
+
+ button.overlays.Cut()
+ var/image/IMG = image(icon, button, icon_state)
+ button.overlays += IMG
+
+/datum/action/item_action/specialist/toggle_laser/can_use_action()
+ var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
+
+ if(owner.is_mob_incapacitated())
+ return FALSE
+
+ if(owner.get_held_item() != sniper_rifle)
+ to_chat(owner, SPAN_WARNING("How do you expect to do this without the sniper rifle in your hand?"))
+ return FALSE
+ return TRUE
+
+/datum/action/item_action/specialist/toggle_laser/action_activate()
+ var/obj/item/weapon/gun/rifle/sniper/sniper_rifle = holder_item
+
+ if(owner.get_held_item() != sniper_rifle)
+ to_chat(owner, SPAN_WARNING("How do you expect to do this without the sniper rifle in your hand?"))
+ return FALSE
+ sniper_rifle.toggle_laser(owner, src)
+
+/obj/item/weapon/gun/rifle/sniper/proc/toggle_laser(mob/user, datum/action/toggling_action)
+ enable_aimed_shot_laser = !enable_aimed_shot_laser
+ to_chat(user, SPAN_NOTICE("You flip a switch on \the [src] and [enable_aimed_shot_laser ? "enable" : "disable"] its targeting laser."))
+ playsound(user, 'sound/machines/click.ogg', 15, TRUE)
+ if(!toggling_action)
+ toggling_action = locate(/datum/action/item_action/specialist/toggle_laser) in actions
+ if(toggling_action)
+ toggling_action.update_button_icon()
+
+/obj/item/weapon/gun/rifle/sniper/verb/toggle_gun_laser()
+ set category = "Weapons"
+ set name = "Toggle Laser"
+ set desc = "Toggles your laser on or off."
+ set src = usr.contents
+
+ var/obj/item/weapon/gun/rifle/sniper/sniper = get_active_firearm(usr)
+ if((sniper == src) && has_aimed_shot)
+ toggle_laser(usr)
+
+//Pow! Headshot.
+
+// end of actions sniper spe can take.
+
+/obj/item/weapon/gun/rifle/sniper/M42A
+ name = "\improper M42A scoped rifle"
+ desc = "A heavy sniper rifle manufactured by Armat Systems. It has a scope system and fires armor penetrating rounds out of a 15-round magazine.\n'Peace Through Superior Firepower'"
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "m42a"
+ item_state = "m42a"
+ unacidable = TRUE
+ indestructible = 1
+
+ fire_sound = 'sound/weapons/gun_sniper.ogg'
+ current_mag = /obj/item/ammo_magazine/sniper
+ force = 12
+ wield_delay = WIELD_DELAY_HORRIBLE //Ends up being 1.6 seconds due to scope
+ zoomdevicename = "scope"
+ attachable_allowed = list(/obj/item/attachable/bipod)
+ starting_attachment_types = list(/obj/item/attachable/sniperbarrel)
+ flags_gun_features = GUN_AUTO_EJECTOR|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER
+ map_specific_decoration = TRUE
+
+ flags_item = TWOHANDED|NO_CRYO_STORE
+
+/obj/item/weapon/gun/rifle/sniper/M42A/verb/toggle_scope_zoom_level()
+ set name = "Toggle Scope Zoom Level"
+ set category = "Weapons"
+ set src in usr
+ var/obj/item/attachable/scope/variable_zoom/S = attachments["rail"]
+ S.toggle_zoom_level()
+
+/obj/item/weapon/gun/rifle/sniper/M42A/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/scope/variable_zoom/S = new(src)
+ S.hidden = TRUE
+ S.flags_attach_features &= ~ATTACH_REMOVABLE
+ S.Attach(src)
+ update_attachable(S.slot)
+
+/obj/item/weapon/gun/rifle/sniper/M42A/set_bullet_traits()
+ LAZYADD(traits_to_give, list(
+ BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
+ ))
+
+/obj/item/weapon/gun/rifle/sniper/M42A/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 39, "muzzle_y" = 17,"rail_x" = 12, "rail_y" = 20, "under_x" = 19, "under_y" = 14, "stock_x" = 19, "stock_y" = 14)
+
+
+/obj/item/weapon/gun/rifle/sniper/M42A/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_7*3)
+ set_burst_amount(BURST_AMOUNT_TIER_1)
+ accuracy_mult = BASE_ACCURACY_MULT * 3 //you HAVE to be able to hit
+ scatter = SCATTER_AMOUNT_TIER_8
+ damage_mult = BASE_BULLET_DAMAGE_MULT
+ recoil = RECOIL_AMOUNT_TIER_5
+
+/obj/item/weapon/gun/rifle/sniper/xm43e1
+ name = "\improper XM43E1 experimental anti-materiel rifle"
+ desc = "An experimental anti-materiel rifle produced by Armat Systems, recently reacquired from the deep storage of an abandoned prototyping facility. This one in particular is currently undergoing field testing. Chambered in 10x99mm Caseless."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi'
+ icon_state = "xm42b"
+ item_state = "xm42b"
+ unacidable = TRUE
+ indestructible = 1
+
+ fire_sound = 'sound/weapons/sniper_heavy.ogg'
+ current_mag = /obj/item/ammo_magazine/sniper/anti_materiel //Renamed from anti-tank to align with new identity/description. Other references have been changed as well. -Kaga
+ force = 12
+ wield_delay = WIELD_DELAY_HORRIBLE //Ends up being 1.6 seconds due to scope
+ zoomdevicename = "scope"
+ attachable_allowed = list(/obj/item/attachable/bipod)
+ flags_gun_features = GUN_AUTO_EJECTOR|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER
+ starting_attachment_types = list(/obj/item/attachable/sniperbarrel)
+ sniper_beam_type = /obj/effect/ebeam/laser/intense
+ sniper_beam_icon = "laser_beam_intense"
+ sniper_lockon_icon = "sniper_lockon_intense"
+
+/obj/item/weapon/gun/rifle/sniper/XM42B/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/scope/variable_zoom/S = new(src)
+ S.icon_state = "pmcscope"
+ S.attach_icon = "pmcscope"
+ S.flags_attach_features &= ~ATTACH_REMOVABLE
+ S.Attach(src)
+ update_attachable(S.slot)
+
+
+/obj/item/weapon/gun/rifle/sniper/XM42B/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 18,"rail_x" = 15, "rail_y" = 19, "under_x" = 20, "under_y" = 15, "stock_x" = 20, "stock_y" = 15)
+
+
+/obj/item/weapon/gun/rifle/sniper/XM42B/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_6 * 6 )//Big boy damage, but it takes a lot of time to fire a shot.
+ //Kaga: Adjusted from 56 (Tier 4, 7*8) -> 30 (Tier 6, 5*6) ticks. 95 really wasn't big-boy damage anymore, although I updated it to 125 to remain consistent with the other 10x99mm caliber weapon (M42C). Now takes only twice as long as the M42A.
+ set_burst_amount(BURST_AMOUNT_TIER_1)
+ accuracy_mult = BASE_ACCURACY_MULT + 2*HIT_ACCURACY_MULT_TIER_10 //Who coded this like this, and why? It just calculates out to 1+1=2. Leaving a note here to check back later.
+ scatter = SCATTER_AMOUNT_TIER_10
+ damage_mult = BASE_BULLET_DAMAGE_MULT
+ recoil = RECOIL_AMOUNT_TIER_1
+
+/obj/item/weapon/gun/rifle/sniper/XM42B/set_bullet_traits()
+ LAZYADD(traits_to_give, list(
+ BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff),
+ BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_penetrating),
+ BULLET_TRAIT_ENTRY_ID("turfs", /datum/element/bullet_trait_damage_boost, 11, GLOB.damage_boost_turfs),
+ BULLET_TRAIT_ENTRY_ID("breaching", /datum/element/bullet_trait_damage_boost, 11, GLOB.damage_boost_breaching),
+ //At 1375 per shot it'll take 1 shot to break resin turfs, and a full mag of 8 to break reinforced walls.
+ BULLET_TRAIT_ENTRY_ID("pylons", /datum/element/bullet_trait_damage_boost, 6, GLOB.damage_boost_pylons)
+ //At 750 per shot it'll take 3 to break a Pylon (1800 HP). No Damage Boost vs other xeno structures yet, those will require a whole new list w/ the damage_boost trait.
+ ))
+
+/*
+//Disabled until an identity is better defined. -Kaga
+/obj/item/weapon/gun/rifle/sniper/M42B/afterattack(atom/target, mob/user, flag)
+ if(able_to_fire(user))
+ if(get_dist(target,user) <= 8)
+ to_chat(user, SPAN_WARNING("The [src.name] beeps, indicating that the target is within an unsafe proximity to the rifle, refusing to fire."))
+ return
+ else ..()
+*/
+
+/obj/item/weapon/gun/rifle/sniper/elite
+ name = "\improper M42C anti-tank sniper rifle"
+ desc = "A high-end superheavy magrail sniper rifle from Weyland-Armat chambered in a specialized variant of the heaviest ammo available, 10x99mm Caseless. This weapon requires a specialized armor rig for recoil mitigation in order to be used effectively."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/wy.dmi'
+ icon_state = "m42c"
+ item_state = "m42c" //NEEDS A TWOHANDED STATE
+
+ fire_sound = 'sound/weapons/sniper_heavy.ogg'
+ current_mag = /obj/item/ammo_magazine/sniper/elite
+ force = 17
+ zoomdevicename = "scope"
+ flags_gun_features = GUN_AUTO_EJECTOR|GUN_WY_RESTRICTED|GUN_SPECIALIST|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER
+ starting_attachment_types = list(/obj/item/attachable/sniperbarrel)
+ sniper_beam_type = /obj/effect/ebeam/laser/intense
+ sniper_beam_icon = "laser_beam_intense"
+ sniper_lockon_icon = "sniper_lockon_intense"
+
+/obj/item/weapon/gun/rifle/sniper/elite/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/scope/S = new(src)
+ S.icon_state = "pmcscope"
+ S.attach_icon = "pmcscope"
+ S.flags_attach_features &= ~ATTACH_REMOVABLE
+ S.Attach(src)
+ update_attachable(S.slot)
+
+/obj/item/weapon/gun/rifle/sniper/elite/set_bullet_traits()
+ LAZYADD(traits_to_give, list(
+ BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
+ ))
+
+/obj/item/weapon/gun/rifle/sniper/elite/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 18,"rail_x" = 15, "rail_y" = 19, "under_x" = 20, "under_y" = 15, "stock_x" = 20, "stock_y" = 15)
+
+/obj/item/weapon/gun/rifle/sniper/elite/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_6*5)
+ set_burst_amount(BURST_AMOUNT_TIER_1)
+ accuracy_mult = BASE_ACCURACY_MULT * 3 //Was previously BAM + HAMT10, similar to the XM42B, and coming out to 1.5? Changed to be consistent with M42A. -Kaga
+ scatter = SCATTER_AMOUNT_TIER_10 //Was previously 8, changed to be consistent with the XM42B.
+ damage_mult = BASE_BULLET_DAMAGE_MULT
+ recoil = RECOIL_AMOUNT_TIER_1
+
+/obj/item/weapon/gun/rifle/sniper/elite/simulate_recoil(total_recoil = 0, mob/user, atom/target)
+ . = ..()
+ if(.)
+ var/mob/living/carbon/human/PMC_sniper = user
+ if(PMC_sniper.lying == 0 && !istype(PMC_sniper.wear_suit,/obj/item/clothing/suit/storage/marine/smartgunner/veteran/pmc) && !istype(PMC_sniper.wear_suit,/obj/item/clothing/suit/storage/marine/veteran))
+ PMC_sniper.visible_message(SPAN_WARNING("[PMC_sniper] is blown backwards from the recoil of the [src.name]!"),SPAN_HIGHDANGER("You are knocked prone by the blowback!"))
+ step(PMC_sniper,turn(PMC_sniper.dir,180))
+ PMC_sniper.apply_effect(5, WEAKEN)
+
+//Type 88 //Based on the actual Dragunov DMR rifle.
+
+/obj/item/weapon/gun/rifle/sniper/svd
+ name = "\improper Type 88 designated marksman rifle"
+ desc = "The standard issue DMR of the UPP, the Type 88 is sought after by competitive shooters and terrorists alike for its high degree of accuracy. Typically loaded with armor-piercing 7.62x54mmR rounds in a 12 round magazine."
+ icon = 'icons/obj/items/weapons/guns/guns_by_faction/upp.dmi'
+ icon_state = "type88"
+ item_state = "type88"
+
+ fire_sound = 'sound/weapons/gun_mg.ogg'
+ current_mag = /obj/item/ammo_magazine/sniper/svd
+ attachable_allowed = list(
+ //Muzzle,
+ /obj/item/attachable/bayonet,
+ /obj/item/attachable/bayonet/upp_replica,
+ /obj/item/attachable/bayonet/upp,
+ //Under,
+ /obj/item/attachable/verticalgrip,
+ /obj/item/attachable/bipod,
+ //Integrated,
+ /obj/item/attachable/type88_barrel,
+ )
+ has_aimed_shot = FALSE
+ flags_gun_features = GUN_AUTO_EJECTOR|GUN_WIELDED_FIRING_ONLY|GUN_AMMO_COUNTER|GUN_CAN_POINTBLANK
+ starting_attachment_types = list()
+ sniper_beam_type = null
+ skill_locked = FALSE
+
+/obj/item/weapon/gun/rifle/sniper/svd/handle_starting_attachment()
+ ..()
+ var/obj/item/attachable/attachie = new /obj/item/attachable/type88_barrel(src)
+ attachie.flags_attach_features &= ~ATTACH_REMOVABLE
+ attachie.Attach(src)
+ update_attachable(attachie.slot)
+
+ var/obj/item/attachable/scope/variable_zoom/integrated/type88sight = new(src)
+ type88sight.flags_attach_features &= ~ATTACH_REMOVABLE
+ type88sight.hidden = TRUE
+ type88sight.Attach(src)
+ update_attachable(type88sight.slot)
+
+/obj/item/weapon/gun/rifle/sniper/svd/set_gun_attachment_offsets()
+ attachable_offset = list("muzzle_x" = 32, "muzzle_y" = 17,"rail_x" = 13, "rail_y" = 19, "under_x" = 26, "under_y" = 14, "stock_x" = 24, "stock_y" = 13, "special_x" = 39, "special_y" = 18)
+
+/obj/item/weapon/gun/rifle/sniper/svd/set_gun_config_values()
+ ..()
+ set_fire_delay(FIRE_DELAY_TIER_6)
+ set_burst_amount(BURST_AMOUNT_TIER_1)
+ accuracy_mult = BASE_ACCURACY_MULT * 3
+ scatter = SCATTER_AMOUNT_TIER_8
+ damage_mult = BASE_BULLET_DAMAGE_MULT
+ recoil = RECOIL_AMOUNT_TIER_5
+ damage_falloff_mult = 0
diff --git a/colonialmarines.dme b/colonialmarines.dme
index f8f1ca101c9a..3f40c156d075 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -2146,9 +2146,13 @@ s// DM Environment file for colonialmarines.dme.
#include "code\modules\projectiles\guns\smartgun.dm"
#include "code\modules\projectiles\guns\smgs.dm"
#include "code\modules\projectiles\guns\souto.dm"
-#include "code\modules\projectiles\guns\specialist.dm"
#include "code\modules\projectiles\guns\flamer\flamer.dm"
#include "code\modules\projectiles\guns\flamer\flameshape.dm"
+#include "code\modules\projectiles\guns\specialist\scout.dm"
+#include "code\modules\projectiles\guns\specialist\sniper.dm"
+#include "code\modules\projectiles\guns\specialist\launcher\grenade_launcher.dm"
+#include "code\modules\projectiles\guns\specialist\launcher\launcher.dm"
+#include "code\modules\projectiles\guns\specialist\launcher\rocket_launcher.dm"
#include "code\modules\projectiles\magazines\flamer.dm"
#include "code\modules\projectiles\magazines\lever_action.dm"
#include "code\modules\projectiles\magazines\misc.dm"
diff --git a/html/changelogs/AutoChangeLog-pr-4482.yml b/html/changelogs/AutoChangeLog-pr-4482.yml
deleted file mode 100644
index e61c2ffce714..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4482.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: "QuickLode"
-delete-after: True
-changes:
- - rscadd: "Allows ext webbing to hold firearms."
- - bugfix: "exosuits which can hold scabbards can hold similar scabbards(ie, machete and katana)"
- - rscdel: "Removes ext webbing from SO Locker"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4492.yml b/html/changelogs/AutoChangeLog-pr-4492.yml
deleted file mode 100644
index 343e17066f8d..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4492.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "QuickLode"
-delete-after: True
-changes:
- - rscdel: "removed Pvts from Anchorpoint QRF (rip)"
- - spellcheck: "fixed a typo in CMB call-in"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4501.yml b/html/changelogs/AutoChangeLog-pr-4501.yml
deleted file mode 100644
index 10ec713dde4c..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4501.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Ben10083"
-delete-after: True
-changes:
- - soundadd: "Multiple new Working Joe voicelines added"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4504.yml b/html/changelogs/AutoChangeLog-pr-4504.yml
deleted file mode 100644
index b1c18937800d..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4504.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Morrow"
-delete-after: True
-changes:
- - qol: "\"Do nothing\" dual wield preference"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4510.yml b/html/changelogs/AutoChangeLog-pr-4510.yml
deleted file mode 100644
index fb623f111bb2..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4510.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-author: "SpartanBobby"
-delete-after: True
-changes:
- - maptweak: "CL now spawns in a hypersleep bay \"Passenger Bay\" it's right next to his office, the CC spawns with him too since his landmark was in the latejoin bay on the lowerdeck"
- - maptweak: "re-arranged PO bunks should allow for better traffic in and out"
- - maptweak: "fixed symmetry issue in north-south CIC hallway"
- - maptweak: "minor warning stripe decal additions around Almayer"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4512.yml b/html/changelogs/AutoChangeLog-pr-4512.yml
deleted file mode 100644
index 676dbb486835..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4512.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Casper"
-delete-after: True
-changes:
- - bugfix: "fixed cameras going invisible on wire cut"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4515.yml b/html/changelogs/AutoChangeLog-pr-4515.yml
deleted file mode 100644
index 8b1ebc33ffa6..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4515.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "Steelpoint"
-delete-after: True
-changes:
- - rscadd: "Synthetic equipment vendor now can vend fuel cannisters and all colour variants of the synthetic utility vest."
- - rscadd: "Synthetic cosmetic vendor now can vend all colour variants of the standard Marine helmet, MP and Combat Technician uniforms, the welder chestrig and security hud glasses."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4522.yml b/html/changelogs/AutoChangeLog-pr-4522.yml
deleted file mode 100644
index cba713b98d4a..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4522.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Huffie56"
-delete-after: True
-changes:
- - refactor: "divide preset into different file for each map."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4525.yml b/html/changelogs/AutoChangeLog-pr-4525.yml
deleted file mode 100644
index 2074207b76a9..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4525.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "BeagleGaming1"
-delete-after: True
-changes:
- - bugfix: "Whiskey Outpost ground map vote works correctly"
- - config: "Removed unnecessary config"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4526.yml b/html/changelogs/AutoChangeLog-pr-4526.yml
deleted file mode 100644
index bb5f16c62bcf..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4526.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Zonespace27"
-delete-after: True
-changes:
- - rscadd: "You can now fold a combi-stick using the new \"collapse combi-stick\" verb and/or keybind. Defaults to the space bar."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4527.yml b/html/changelogs/AutoChangeLog-pr-4527.yml
deleted file mode 100644
index c5c4b592d8d5..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4527.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Zonespace27"
-delete-after: True
-changes:
- - bugfix: "Using a Yautja relay beacon now properly decloaks you"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4528.yml b/html/changelogs/AutoChangeLog-pr-4528.yml
deleted file mode 100644
index d00e7cdfa8e5..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4528.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Zonespace27"
-delete-after: True
-changes:
- - bugfix: "Picking up a dropped pred bracer will no longer leave it stuck to your hand."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4529.yml b/html/changelogs/AutoChangeLog-pr-4529.yml
deleted file mode 100644
index d901f8e0281b..000000000000
--- a/html/changelogs/AutoChangeLog-pr-4529.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Zonespace27"
-delete-after: True
-changes:
- - bugfix: "Yautja can no longer mark xenoes as honorable"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4541.yml b/html/changelogs/AutoChangeLog-pr-4541.yml
new file mode 100644
index 000000000000..3c8ce797677e
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4541.yml
@@ -0,0 +1,4 @@
+author: "QuickLode"
+delete-after: True
+changes:
+ - bugfix: "Allows M46C prototype rifle to accept standard M41A MK2 rubber munitions"
\ No newline at end of file
diff --git a/html/changelogs/archive/2023-09.yml b/html/changelogs/archive/2023-09.yml
index 4d0e20f79337..6dc9203f23d8 100644
--- a/html/changelogs/archive/2023-09.yml
+++ b/html/changelogs/archive/2023-09.yml
@@ -404,3 +404,51 @@
now properly emphasizes why it is a threat to Marines, and a benefit to Xenos.
TheGamerdk:
- qol: You can no longer doom yourself by joining as a crit xeno
+2023-09-29:
+ BeagleGaming1:
+ - bugfix: Whiskey Outpost ground map vote works correctly
+ - config: Removed unnecessary config
+ Ben10083:
+ - soundadd: Multiple new Working Joe voicelines added
+ Casper:
+ - bugfix: fixed cameras going invisible on wire cut
+ Huffie56:
+ - refactor: divide preset into different file for each map.
+ Morrow:
+ - qol: '"Do nothing" dual wield preference'
+ QuickLode:
+ - rscdel: removed Pvts from Anchorpoint QRF (rip)
+ - spellcheck: fixed a typo in CMB call-in
+ - rscadd: Allows ext webbing to hold firearms.
+ - bugfix: exosuits which can hold scabbards can hold similar scabbards(ie, machete
+ and katana)
+ - rscdel: Removes ext webbing from SO Locker
+ SpartanBobby:
+ - maptweak: CL now spawns in a hypersleep bay "Passenger Bay" it's right next to
+ his office, the CC spawns with him too since his landmark was in the latejoin
+ bay on the lowerdeck
+ - maptweak: re-arranged PO bunks should allow for better traffic in and out
+ - maptweak: fixed symmetry issue in north-south CIC hallway
+ - maptweak: minor warning stripe decal additions around Almayer
+ Steelpoint:
+ - rscadd: Synthetic equipment vendor now can vend fuel cannisters and all colour
+ variants of the synthetic utility vest.
+ - rscadd: Synthetic cosmetic vendor now can vend all colour variants of the standard
+ Marine helmet, MP and Combat Technician uniforms, the welder chestrig and security
+ hud glasses.
+ Zonespace27:
+ - bugfix: Picking up a dropped pred bracer will no longer leave it stuck to your
+ hand.
+ - bugfix: Yautja can no longer mark xenoes as honorable
+ - rscadd: You can now fold a combi-stick using the new "collapse combi-stick" verb
+ and/or keybind. Defaults to the space bar.
+ - bugfix: Using a Yautja relay beacon now properly decloaks you
+2023-09-30:
+ Drathek:
+ - bugfix: Join as xeno no longer offers buried larva spawns if there are larva queue
+ candidates
+ Huffie56:
+ - refactor: Cleaning specialist.dm file
+ Morrow:
+ - rscdel: Removed more clown gear from maps
+ - bugfix: Fixed taking control of crit xenos
diff --git a/maps/map_files/FOP_v3_Sciannex/Fiorina_SciAnnex.dmm b/maps/map_files/FOP_v3_Sciannex/Fiorina_SciAnnex.dmm
index 0327fcdd21bc..0d4ed21934d0 100644
--- a/maps/map_files/FOP_v3_Sciannex/Fiorina_SciAnnex.dmm
+++ b/maps/map_files/FOP_v3_Sciannex/Fiorina_SciAnnex.dmm
@@ -13011,7 +13011,6 @@
/turf/open/floor/plating/prison,
/area/fiorina/lz/near_lzI)
"hQH" = (
-/obj/item/reagent_container/food/snacks/clownstears,
/obj/structure/closet/crate,
/turf/open/floor/plating/prison,
/area/fiorina/station/civres_blue)
@@ -13096,9 +13095,7 @@
/area/fiorina/lz/near_lzII)
"hTr" = (
/obj/structure/closet,
-/obj/item/clothing/mask/gas/clown_hat,
/obj/effect/spawner/random/gun/shotgun/midchance,
-/obj/item/clothing/under/marine/ucf_clown,
/turf/open/floor/plating/prison,
/area/fiorina/maintenance)
"hTs" = (
@@ -15343,7 +15340,6 @@
/area/fiorina/lz/near_lzII)
"jnr" = (
/obj/structure/filingcabinet,
-/obj/item/card/data/clown,
/obj/effect/landmark/objective_landmark/medium,
/turf/open/floor/prison{
dir = 4;
@@ -26131,8 +26127,6 @@
/area/fiorina/tumor/aux_engi)
"pON" = (
/obj/structure/closet,
-/obj/item/storage/backpack/clown,
-/obj/item/toy/bikehorn,
/turf/open/floor/prison{
dir = 4;
icon_state = "bluecorner"