diff --git a/code/__DEFINES/~skyrat_defines/gun.dm b/code/__DEFINES/~skyrat_defines/gun.dm
new file mode 100644
index 00000000000..05ce5118a30
--- /dev/null
+++ b/code/__DEFINES/~skyrat_defines/gun.dm
@@ -0,0 +1,3 @@
+#define SELECT_SEMI_AUTOMATIC 1
+#define SELECT_BURST_SHOT 2
+#define SELECT_FULLY_AUTOMATIC 3
diff --git a/code/__DEFINES/~skyrat_defines/signals.dm b/code/__DEFINES/~skyrat_defines/signals.dm
index b04618b41e1..e362ae9f521 100644
--- a/code/__DEFINES/~skyrat_defines/signals.dm
+++ b/code/__DEFINES/~skyrat_defines/signals.dm
@@ -1,9 +1,15 @@
-// Movable signals
+//Movable signals
+///When someone talks into a radio
#define COMSIG_MOVABLE_RADIO_TALK_INTO "movable_radio_talk_into" //from radio talk_into(): (obj/item/radio/radio, message, channel, list/spans, datum/language/language, direct)
-// Mob signals
-// Living signals
+//Mob signals
+///Resting position for living mob updated
#define COMSIG_LIVING_UPDATED_RESTING "living_updated_resting" //from base of (/mob/living/proc/update_resting): (resting)
-
-//Horror form bombastic flag
+///Horror form bombastic flag
#define COMSIG_HORRORFORM_EXPLODE "horrorform_explode"
+
+//Gun signals
+///When a gun is switched to automatic fire mode
+#define COMSIG_GUN_AUTOFIRE_SELECTED "gun_autofire_selected"
+///When a gun is switched off of automatic fire mode
+#define COMSIG_GUN_AUTOFIRE_DESELECTED "gun_autofire_deselected"
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 0447d7d4a81..8bfe791c261 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -201,6 +201,8 @@
name = "Toggle Hood"
/datum/action/item_action/toggle_firemode
+ icon_icon = 'modular_skyrat/master_files/icons/mob/actions/actions_items.dmi' //SKYRAT EDIT ADDITION
+ button_icon_state = "fireselect_no" //SKYRAT EDIT ADDITION
name = "Toggle Firemode"
/datum/action/item_action/rcl_col
@@ -675,7 +677,7 @@
desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps."
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "jetboot"
-
+
/datum/action/item_action/bhop/brocket
name = "Activate Rocket Boots"
desc = "Activates the boot's rocket propulsion system, allowing the user to hurl themselves great distances."
diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm
index 0da742c8a1f..40f81a435f4 100644
--- a/code/datums/components/fullauto.dm
+++ b/code/datums/components/fullauto.dm
@@ -1,3 +1,4 @@
+/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR FULLAUTO.DM
#define AUTOFIRE_MOUSEUP 0
#define AUTOFIRE_MOUSEDOWN 1
@@ -275,3 +276,4 @@
#undef AUTOFIRE_MOUSEUP
#undef AUTOFIRE_MOUSEDOWN
+*/
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index b32b81b838c..eaedabdb140 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -1,4 +1,4 @@
-
+/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR GUN.DM
#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4
#define FIRING_PIN_REMOVAL_DELAY 50
@@ -718,3 +718,4 @@
#undef FIRING_PIN_REMOVAL_DELAY
#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER
+*/
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index dde5ef6cd69..2698f289c33 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -1,3 +1,4 @@
+/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR AUTOMATIC.DM
/obj/item/gun/ballistic/automatic
w_class = WEIGHT_CLASS_NORMAL
can_suppress = TRUE
@@ -63,7 +64,7 @@
var/datum/action/A = X
A.UpdateButtonIcon()
-/obj/item/gun/ballistic/automatic/c20r //ICON OVERRIDEN IN SKYRAT AESTHETICS - SEE MODULE
+/obj/item/gun/ballistic/automatic/c20r
name = "\improper C-20r SMG"
desc = "A bullpup three-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp."
icon_state = "c20r"
@@ -136,7 +137,7 @@
mag_display = TRUE
rack_sound = 'sound/weapons/gun/pistol/slide_lock.ogg'
-/obj/item/gun/ballistic/automatic/m90 //ICON OVERRIDEN IN SKYRAT AESTHETICS - SEE MODULE
+/obj/item/gun/ballistic/automatic/m90
name = "\improper M-90gl Carbine"
desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher which can be fired using right click."
icon_state = "m90"
@@ -275,7 +276,6 @@
/obj/item/gun/ballistic/automatic/l6_saw/AltClick(mob/user)
- . = ..() //SKYRAT EDIT ADDITION - GUNSGALORE
if(!user.canUseTopic(src))
return
cover_open = !cover_open
@@ -359,8 +359,7 @@
/obj/item/gun/ballistic/automatic/surplus
name = "Surplus Rifle"
-// desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm ammo and its bulky frame prevents one-hand firing." // SKYRAT EDIT: Original
- desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm Magnum ammo and its bulky frame prevents one-hand firing." // SKYRAT EDIT: Calibre rename
+ desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm ammo and its bulky frame prevents one-hand firing."
icon_state = "surplus"
inhand_icon_state = "moistnugget"
worn_icon_state = null
@@ -391,3 +390,4 @@
actions_types = list()
fire_sound = 'sound/weapons/laser.ogg'
casing_ejector = FALSE
+*/
diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm
index 558157a6ad5..d838bfaca00 100644
--- a/code/modules/projectiles/guns/ballistic/launchers.dm
+++ b/code/modules/projectiles/guns/ballistic/launchers.dm
@@ -39,7 +39,7 @@
mag_type = /obj/item/ammo_box/magazine/m75
burst_size = 1
fire_delay = 0
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE
casing_ejector = FALSE
/obj/item/gun/ballistic/rocketlauncher
diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
index 24e026d8737..698f47be167 100644
--- a/code/modules/projectiles/guns/ballistic/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -8,7 +8,7 @@
can_suppress = TRUE
burst_size = 1
fire_delay = 0
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE
bolt_type = BOLT_TYPE_LOCKING
fire_sound = 'sound/weapons/gun/pistol/shot.ogg'
dry_fire_sound = 'sound/weapons/gun/pistol/dry_fire.ogg'
@@ -81,7 +81,7 @@
burst_size = 3
fire_delay = 1
spread = 10
- actions_types = list(/datum/action/item_action/toggle_firemode)
+ //actions_types = list(/datum/action/item_action/toggle_firemode) SKYRAT EDIT REMOVAL
suppressor_x_offset = 6
/obj/item/gun/ballistic/automatic/pistol/stickman
diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
index 80c0138a3b0..853cd0f7d79 100644
--- a/code/modules/projectiles/guns/ballistic/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -128,7 +128,7 @@
fire_delay = 0
pin = /obj/item/firing_pin/implant/pindicate
fire_sound = 'sound/weapons/gun/shotgun/shot_alt.ogg'
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE
mag_display = TRUE
empty_indicator = TRUE
empty_alarm = TRUE
diff --git a/code/modules/projectiles/guns/energy/laser_gatling.dm b/code/modules/projectiles/guns/energy/laser_gatling.dm
index b82c983c901..18f525ee87b 100644
--- a/code/modules/projectiles/guns/energy/laser_gatling.dm
+++ b/code/modules/projectiles/guns/energy/laser_gatling.dm
@@ -109,6 +109,8 @@
item_flags = NEEDS_PERMIT | SLOWS_WHILE_IN_HAND
can_charge = FALSE
var/obj/item/minigunpack/ammo_pack
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_FULLY_AUTOMATIC)//SKYRAT EDIT ADDITION
+
/obj/item/gun/energy/minigun/Initialize()
if(istype(loc, /obj/item/minigunpack)) //We should spawn inside an ammo pack so let's use that one.
@@ -121,7 +123,7 @@
/obj/item/gun/energy/minigun/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_blocker)
- AddComponent(/datum/component/automatic_fire, 0.2 SECONDS)
+ //AddComponent(/datum/component/automatic_fire, 0.2 SECONDS) SKYRAT EDIT REMOVAL
/obj/item/gun/energy/minigun/attack_self(mob/living/user)
return
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 998773f5079..5b9255a6fcd 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -380,4 +380,4 @@
/obj/item/gun/energy/tesla_cannon/ComponentInitialize()
. = ..()
- AddComponent(/datum/component/automatic_fire, 0.1 SECONDS)
+ //AddComponent(/datum/component/automatic_fire, 0.1 SECONDS) SKYRAT EDIT REMOVAL
diff --git a/modular_skyrat/master_files/code/datums/components/fullauto.dm b/modular_skyrat/master_files/code/datums/components/fullauto.dm
new file mode 100644
index 00000000000..7286e1d36de
--- /dev/null
+++ b/modular_skyrat/master_files/code/datums/components/fullauto.dm
@@ -0,0 +1,278 @@
+#define AUTOFIRE_MOUSEUP 0
+#define AUTOFIRE_MOUSEDOWN 1
+
+/datum/component/automatic_fire
+ var/client/clicker
+ var/mob/living/shooter
+ var/atom/target
+ var/turf/target_loc //For dealing with locking on targets due to BYOND engine limitations (the mouse input only happening when mouse moves).
+ var/autofire_stat = AUTOFIRE_STAT_IDLE
+ var/mouse_parameters
+ var/autofire_shot_delay = 0.3 SECONDS //Time between individual shots.
+ var/mouse_status = AUTOFIRE_MOUSEUP //This seems hacky but there can be two MouseDown() without a MouseUp() in between if the user holds click and uses alt+tab, printscreen or similar.
+
+ COOLDOWN_DECLARE(next_shot_cd)
+
+/datum/component/automatic_fire/Initialize(_autofire_shot_delay)
+ . = ..()
+ if(!isgun(parent))
+ return COMPONENT_INCOMPATIBLE
+ var/obj/item/gun = parent
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/wake_up)
+ RegisterSignal(parent, COMSIG_GUN_AUTOFIRE_SELECTED, .proc/wake_up)
+ RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED, COMSIG_GUN_AUTOFIRE_DESELECTED), .proc/autofire_off)
+ if(_autofire_shot_delay)
+ autofire_shot_delay = _autofire_shot_delay
+ if(ismob(gun.loc))
+ var/mob/user = gun.loc
+ wake_up(src, user)
+
+
+/datum/component/automatic_fire/Destroy()
+ UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_ITEM_DROPPED, COMSIG_GUN_AUTOFIRE_DESELECTED))
+ autofire_off()
+ return ..()
+
+/datum/component/automatic_fire/process(delta_time)
+ if(!(autofire_stat & AUTOFIRE_STAT_FIRING))
+ STOP_PROCESSING(SSprojectiles, src)
+ return
+
+ if(!COOLDOWN_FINISHED(src, next_shot_cd))
+ return
+
+ process_shot()
+
+/datum/component/automatic_fire/proc/wake_up(datum/source, mob/user, slot)
+ SIGNAL_HANDLER
+
+ if(autofire_stat & (AUTOFIRE_STAT_ALERT))
+ return //We've updated the firemode. No need for more.
+ if(autofire_stat & AUTOFIRE_STAT_FIRING)
+ stop_autofiring() //Let's stop shooting to avoid issues.
+ return
+
+ var/obj/item/gun/G = parent
+
+ if(iscarbon(user))
+ var/mob/living/carbon/shooter = user
+ if(shooter.is_holding(parent) && G.fire_select == SELECT_FULLY_AUTOMATIC)
+ autofire_on(shooter.client)
+ else
+ autofire_off()
+
+// There is a gun and there is a user wielding it. The component now waits for the mouse click.
+/datum/component/automatic_fire/proc/autofire_on(client/usercli)
+ SIGNAL_HANDLER
+ if(autofire_stat & (AUTOFIRE_STAT_ALERT|AUTOFIRE_STAT_FIRING))
+ return
+ autofire_stat = AUTOFIRE_STAT_ALERT
+ clicker = usercli
+ shooter = clicker.mob
+ RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDOWN, .proc/on_mouse_down)
+ RegisterSignal(shooter, COMSIG_MOB_LOGOUT, .proc/autofire_off)
+ if(!QDELETED(shooter))
+ UnregisterSignal(shooter, COMSIG_MOB_LOGIN)
+ parent.RegisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN, /obj/item/gun/.proc/autofire_bypass_check)
+ parent.RegisterSignal(parent, COMSIG_AUTOFIRE_SHOT, /obj/item/gun/.proc/do_autofire)
+
+
+/datum/component/automatic_fire/proc/autofire_off(datum/source)
+ SIGNAL_HANDLER
+ if(autofire_stat & (AUTOFIRE_STAT_IDLE))
+ return
+ if(autofire_stat & AUTOFIRE_STAT_FIRING)
+ stop_autofiring()
+
+ autofire_stat = AUTOFIRE_STAT_IDLE
+
+ if(!QDELETED(clicker))
+ UnregisterSignal(clicker, list(COMSIG_CLIENT_MOUSEDOWN, COMSIG_CLIENT_MOUSEUP, COMSIG_CLIENT_MOUSEDRAG))
+ mouse_status = AUTOFIRE_MOUSEUP //In regards to the component there's no click anymore to care about.
+ clicker = null
+ RegisterSignal(shooter, COMSIG_MOB_LOGIN, .proc/on_client_login)
+ if(!QDELETED(shooter))
+ UnregisterSignal(shooter, COMSIG_MOB_LOGOUT)
+ shooter = null
+ parent.UnregisterSignal(parent, COMSIG_AUTOFIRE_SHOT)
+ parent.UnregisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN)
+
+/datum/component/automatic_fire/proc/on_client_login(mob/source)
+ SIGNAL_HANDLER
+ if(!source.client)
+ return
+ if(source.is_holding(parent))
+ autofire_on(source.client)
+
+/datum/component/automatic_fire/proc/on_mouse_down(client/source, atom/_target, turf/location, control, params)
+ var/list/modifiers = params2list(params) //If they're shift+clicking, for example, let's not have them accidentally shoot.
+
+ if(LAZYACCESS(modifiers, SHIFT_CLICK))
+ return
+ if(LAZYACCESS(modifiers, CTRL_CLICK))
+ return
+ if(LAZYACCESS(modifiers, MIDDLE_CLICK))
+ return
+ if(LAZYACCESS(modifiers, RIGHT_CLICK))
+ return
+ if(LAZYACCESS(modifiers, ALT_CLICK))
+ return
+ if(source.mob.throw_mode)
+ return
+ if(!isturf(source.mob.loc)) //No firing inside lockers and stuff.
+ return
+ if(get_dist(source.mob, _target) < 2) //Adjacent clicking.
+ return
+
+ if(isnull(location)) //Clicking on a screen object.
+ if(_target.plane != CLICKCATCHER_PLANE) //The clickcatcher is a special case. We want the click to trigger then, under it.
+ return //If we click and drag on our worn backpack, for example, we want it to open instead.
+ _target = params2turf(modifiers["screen-loc"], get_turf(source.eye), source)
+ if(!_target)
+ CRASH("Failed to get the turf under clickcatcher")
+
+ if(SEND_SIGNAL(src, COMSIG_AUTOFIRE_ONMOUSEDOWN, source, _target, location, control, params) & COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS)
+ return
+
+ source.click_intercept_time = world.time //From this point onwards Click() will no longer be triggered.
+
+ if(autofire_stat & (AUTOFIRE_STAT_IDLE))
+ CRASH("on_mouse_down() called with [autofire_stat] autofire_stat")
+ if(autofire_stat & AUTOFIRE_STAT_FIRING)
+ stop_autofiring() //This can happen if we click and hold and then alt+tab, printscreen or other such action. MouseUp won't be called then and it will keep autofiring.
+
+ target = _target
+ target_loc = get_turf(target)
+ mouse_parameters = params
+ start_autofiring()
+
+
+//Dakka-dakka
+/datum/component/automatic_fire/proc/start_autofiring()
+ if(autofire_stat == AUTOFIRE_STAT_FIRING)
+ return //Already pew-pewing.
+ autofire_stat = AUTOFIRE_STAT_FIRING
+
+ clicker.mouse_override_icon = 'icons/effects/mouse_pointers/weapon_pointer.dmi'
+ clicker.mouse_pointer_icon = clicker.mouse_override_icon
+
+ if(mouse_status == AUTOFIRE_MOUSEUP) //See mouse_status definition for the reason for this.
+ RegisterSignal(clicker, COMSIG_CLIENT_MOUSEUP, .proc/on_mouse_up)
+ mouse_status = AUTOFIRE_MOUSEDOWN
+
+ RegisterSignal(shooter, COMSIG_MOB_SWAP_HANDS, .proc/stop_autofiring)
+
+ if(isgun(parent))
+ var/obj/item/gun/shoota = parent
+ if(!shoota.on_autofire_start(shooter)) //This is needed because the minigun has a do_after before firing and signals are async.
+ stop_autofiring()
+ return
+ if(autofire_stat != AUTOFIRE_STAT_FIRING)
+ return //Things may have changed while on_autofire_start() was being processed, due to do_after's sleep.
+
+ if(!process_shot()) //First shot is processed instantly.
+ return //If it fails, such as when the gun is empty, then there's no need to schedule a second shot.
+
+ START_PROCESSING(SSprojectiles, src)
+ RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG, .proc/on_mouse_drag)
+
+
+/datum/component/automatic_fire/proc/on_mouse_up(datum/source, atom/object, turf/location, control, params)
+ SIGNAL_HANDLER
+ UnregisterSignal(clicker, COMSIG_CLIENT_MOUSEUP)
+ mouse_status = AUTOFIRE_MOUSEUP
+ if(autofire_stat == AUTOFIRE_STAT_FIRING)
+ stop_autofiring()
+ return COMPONENT_CLIENT_MOUSEUP_INTERCEPT
+
+
+/datum/component/automatic_fire/proc/stop_autofiring(datum/source, atom/object, turf/location, control, params)
+ SIGNAL_HANDLER
+ switch(autofire_stat)
+ if(AUTOFIRE_STAT_IDLE, AUTOFIRE_STAT_ALERT)
+ return
+ STOP_PROCESSING(SSprojectiles, src)
+ autofire_stat = AUTOFIRE_STAT_ALERT
+ if(clicker)
+ clicker.mouse_override_icon = null
+ clicker.mouse_pointer_icon = clicker.mouse_override_icon
+ UnregisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG)
+ if(!QDELETED(shooter))
+ UnregisterSignal(shooter, COMSIG_MOB_SWAP_HANDS)
+ target = null
+ target_loc = null
+ mouse_parameters = null
+
+/datum/component/automatic_fire/proc/on_mouse_drag(client/source, atom/src_object, atom/over_object, turf/src_location, turf/over_location, src_control, over_control, params)
+ SIGNAL_HANDLER
+ if(isnull(over_location)) //This happens when the mouse is over an inventory or screen object, or on entering deep darkness, for example.
+ var/list/modifiers = params2list(params)
+ var/new_target = params2turf(modifiers["screen-loc"], get_turf(source.eye), source)
+ mouse_parameters = params
+ if(!new_target)
+ if(QDELETED(target)) //No new target acquired, and old one was deleted, get us out of here.
+ stop_autofiring()
+ CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]. Old target was incidentally QDELETED.")
+ target = get_turf(target) //If previous target wasn't a turf, let's turn it into one to avoid locking onto a potentially moving target.
+ target_loc = target
+ CRASH("on_mouse_drag failed to get the turf under screen object [over_object.type]")
+ target = new_target
+ target_loc = new_target
+ return
+ target = over_object
+ target_loc = get_turf(over_object)
+ mouse_parameters = params
+
+
+/datum/component/automatic_fire/proc/process_shot()
+ if(autofire_stat != AUTOFIRE_STAT_FIRING)
+ return
+ if(QDELETED(target) || get_turf(target) != target_loc) //Target moved or got destroyed since we last aimed.
+ target = target_loc //So we keep firing on the emptied tile until we move our mouse and find a new target.
+ if(get_dist(shooter, target) <= 0)
+ target = get_step(shooter, shooter.dir) //Shoot in the direction faced if the mouse is on the same tile as we are.
+ target_loc = target
+ else if(!in_view_range(shooter, target))
+ stop_autofiring() //Elvis has left the building.
+ return FALSE
+ shooter.face_atom(target)
+ COOLDOWN_START(src, next_shot_cd, autofire_shot_delay)
+ if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS)
+ return TRUE
+ stop_autofiring()
+ return FALSE
+
+// Gun procs.
+
+/obj/item/gun/proc/on_autofire_start(mob/living/shooter)
+ if(!can_shoot(shooter) || !can_trigger_gun(shooter) || semicd)
+ return FALSE
+ var/obj/item/bodypart/other_hand = shooter.has_hand_for_held_index(shooter.get_inactive_hand_index())
+ if(weapon_weight == WEAPON_HEAVY && (shooter.get_inactive_held_item() || !other_hand))
+ to_chat(shooter, "You need two hands to fire [src]!")
+ return FALSE
+ return TRUE
+
+
+/obj/item/gun/proc/autofire_bypass_check(datum/source, client/clicker, atom/target, turf/location, control, params)
+ SIGNAL_HANDLER
+ if(clicker.mob.get_active_held_item() != src)
+ return COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS
+
+
+/obj/item/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, params)
+ SIGNAL_HANDLER_DOES_SLEEP
+ if(!can_shoot())
+ shoot_with_empty_chamber(shooter)
+ return NONE
+ var/obj/item/gun/akimbo_gun = shooter.get_inactive_held_item()
+ var/bonus_spread = 0
+ if(istype(akimbo_gun) && weapon_weight < WEAPON_MEDIUM)
+ if(akimbo_gun.weapon_weight < WEAPON_MEDIUM && akimbo_gun.can_trigger_gun(shooter))
+ bonus_spread = dual_wield_spread
+ addtimer(CALLBACK(akimbo_gun, /obj/item/gun.proc/process_fire, target, shooter, TRUE, params, null, bonus_spread), 1)
+ process_fire(target, shooter, TRUE, params, null, bonus_spread)
+ return COMPONENT_AUTOFIRE_SHOT_SUCCESS //All is well, we can continue shooting.
+
+#undef AUTOFIRE_MOUSEUP
+#undef AUTOFIRE_MOUSEDOWN
diff --git a/modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm b/modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm
new file mode 100644
index 00000000000..a105ba6245b
--- /dev/null
+++ b/modular_skyrat/master_files/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -0,0 +1,328 @@
+/obj/item/gun/ballistic/automatic
+ w_class = WEIGHT_CLASS_NORMAL
+ can_suppress = TRUE
+ burst_size = 3
+ fire_delay = 2
+ semi_auto = TRUE
+ fire_sound = 'sound/weapons/gun/smg/shot.ogg'
+ fire_sound_volume = 90
+ vary_fire_sound = FALSE
+ rack_sound = 'sound/weapons/gun/smg/smgrack.ogg'
+ suppressed_sound = 'sound/weapons/gun/smg/shot_suppressed.ogg'
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT, SELECT_FULLY_AUTOMATIC)
+
+/obj/item/gun/ballistic/automatic/proto
+ name = "\improper Nanotrasen Saber SMG"
+ desc = "A prototype three-round burst 9mm submachine gun, designated 'SABR'. Has a threaded barrel for suppressors."
+ icon_state = "saber"
+ selector_switch_icon = TRUE
+ mag_display = TRUE
+ empty_indicator = TRUE
+ mag_type = /obj/item/ammo_box/magazine/smgm9mm
+ pin = null
+ bolt_type = BOLT_TYPE_LOCKING
+ show_bolt_icon = FALSE
+
+/obj/item/gun/ballistic/automatic/proto/unrestricted
+ pin = /obj/item/firing_pin
+
+/obj/item/gun/ballistic/automatic/c20r
+ name = "\improper C-20r SMG"
+ desc = "A bullpup three-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp."
+ icon_state = "c20r"
+ inhand_icon_state = "c20r"
+ selector_switch_icon = TRUE
+ mag_type = /obj/item/ammo_box/magazine/smgm45
+ fire_delay = 2
+ burst_size = 3
+ pin = /obj/item/firing_pin/implant/pindicate
+ can_bayonet = TRUE
+ knife_x_offset = 26
+ knife_y_offset = 12
+ mag_display = TRUE
+ mag_display_ammo = TRUE
+ empty_indicator = TRUE
+
+/obj/item/gun/ballistic/automatic/c20r/update_overlays()
+ . = ..()
+ if(!chambered && empty_indicator)
+ . += "[icon_state]_empty"
+
+/obj/item/gun/ballistic/automatic/c20r/unrestricted
+ pin = /obj/item/firing_pin
+
+/obj/item/gun/ballistic/automatic/c20r/Initialize()
+ . = ..()
+ update_appearance()
+
+/obj/item/gun/ballistic/automatic/wt550
+ name = "security auto rifle"
+ desc = "An outdated personal defence weapon. Uses 4.6x30mm rounds and is designated the WT-550 Automatic Rifle."
+ icon_state = "wt550"
+ w_class = WEIGHT_CLASS_BULKY
+ inhand_icon_state = "arg"
+ mag_type = /obj/item/ammo_box/magazine/wt550m9
+ fire_delay = 2
+ can_suppress = FALSE
+ burst_size = 0
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
+ can_bayonet = TRUE
+ knife_x_offset = 25
+ knife_y_offset = 12
+ mag_display = TRUE
+ mag_display_ammo = TRUE
+ empty_indicator = TRUE
+
+/obj/item/gun/ballistic/automatic/plastikov
+ name = "\improper PP-95 SMG"
+ desc = "An ancient 9mm submachine gun pattern updated and simplified to lower costs, though perhaps simplified too much."
+ icon_state = "plastikov"
+ inhand_icon_state = "plastikov"
+ mag_type = /obj/item/ammo_box/magazine/plastikov9mm
+ burst_size = 5
+ spread = 25
+ can_suppress = FALSE
+ projectile_damage_multiplier = 0.35 //It's like 10.5 damage per bullet, it's close enough to 10 shots
+ mag_display = TRUE
+ empty_indicator = TRUE
+ fire_sound = 'sound/weapons/gun/smg/shot_alt.ogg'
+
+/obj/item/gun/ballistic/automatic/mini_uzi
+ name = "\improper Type U3 Uzi"
+ desc = "A lightweight, burst-fire submachine gun, for when you really want someone dead. Uses 9mm rounds."
+ icon_state = "miniuzi"
+ mag_type = /obj/item/ammo_box/magazine/uzim9mm
+ burst_size = 2
+ bolt_type = BOLT_TYPE_OPEN
+ show_bolt_icon = FALSE
+ mag_display = TRUE
+ rack_sound = 'sound/weapons/gun/pistol/slide_lock.ogg'
+
+/obj/item/gun/ballistic/automatic/m90
+ name = "\improper M-90gl Carbine"
+ desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher which can be fired using right click."
+ icon_state = "m90"
+ w_class = WEIGHT_CLASS_BULKY
+ inhand_icon_state = "m90"
+ selector_switch_icon = TRUE
+ mag_type = /obj/item/ammo_box/magazine/m556
+ can_suppress = FALSE
+ var/obj/item/gun/ballistic/revolver/grenadelauncher/underbarrel
+ burst_size = 3
+ fire_delay = 2
+ spread = 5
+ pin = /obj/item/firing_pin/implant/pindicate
+ mag_display = TRUE
+ empty_indicator = TRUE
+ fire_sound = 'sound/weapons/gun/smg/shot_alt.ogg'
+
+/obj/item/gun/ballistic/automatic/m90/Initialize()
+ . = ..()
+ underbarrel = new /obj/item/gun/ballistic/revolver/grenadelauncher(src)
+ update_appearance()
+
+/obj/item/gun/ballistic/automatic/m90/unrestricted
+ pin = /obj/item/firing_pin
+
+/obj/item/gun/ballistic/automatic/m90/unrestricted/Initialize()
+ . = ..()
+ underbarrel = new /obj/item/gun/ballistic/revolver/grenadelauncher/unrestricted(src)
+ update_appearance()
+
+/obj/item/gun/ballistic/automatic/m90/afterattack_secondary(atom/target, mob/living/user, flag, params)
+ underbarrel.afterattack(target, user, flag, params)
+ return SECONDARY_ATTACK_CONTINUE_CHAIN
+
+/obj/item/gun/ballistic/automatic/m90/attackby(obj/item/A, mob/user, params)
+ if(istype(A, /obj/item/ammo_casing))
+ if(istype(A, underbarrel.magazine.ammo_type))
+ underbarrel.attack_self(user)
+ underbarrel.attackby(A, user, params)
+ else
+ ..()
+
+/obj/item/gun/ballistic/automatic/tommygun
+ name = "\improper Thompson SMG"
+ desc = "Based on the classic 'Chicago Typewriter'."
+ icon_state = "tommygun"
+ inhand_icon_state = "shotgun"
+ selector_switch_icon = TRUE
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = 0
+ mag_type = /obj/item/ammo_box/magazine/tommygunm45
+ can_suppress = FALSE
+ burst_size = 4
+ fire_delay = 1
+ bolt_type = BOLT_TYPE_OPEN
+ empty_indicator = TRUE
+ show_bolt_icon = FALSE
+
+/obj/item/gun/ballistic/automatic/ar
+ name = "\improper NT-ARG 'Boarder'"
+ desc = "A robust assault rifle used by Nanotrasen fighting forces."
+ icon_state = "arg"
+ inhand_icon_state = "arg"
+ slot_flags = 0
+ mag_type = /obj/item/ammo_box/magazine/m556
+ can_suppress = FALSE
+ burst_size = 3
+ fire_delay = 1
+
+
+// L6 SAW //
+
+/obj/item/gun/ballistic/automatic/l6_saw
+ name = "\improper L6 SAW"
+ desc = "A heavily modified 7.12x82mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation."
+ icon_state = "l6"
+ inhand_icon_state = "l6"
+ base_icon_state = "l6"
+ w_class = WEIGHT_CLASS_HUGE
+ slot_flags = 0
+ mag_type = /obj/item/ammo_box/magazine/mm712x82
+ weapon_weight = WEAPON_HEAVY
+ burst_size = 1
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_FULLY_AUTOMATIC)
+ can_suppress = FALSE
+ spread = 7
+ pin = /obj/item/firing_pin/implant/pindicate
+ bolt_type = BOLT_TYPE_OPEN
+ show_bolt_icon = FALSE
+ mag_display = TRUE
+ mag_display_ammo = TRUE
+ tac_reloads = FALSE
+ fire_sound = 'sound/weapons/gun/l6/shot.ogg'
+ rack_sound = 'sound/weapons/gun/l6/l6_rack.ogg'
+ suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg'
+ var/cover_open = FALSE
+
+/obj/item/gun/ballistic/automatic/l6_saw/unrestricted
+ pin = /obj/item/firing_pin
+
+/obj/item/gun/ballistic/automatic/l6_saw/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_updates_onmob)
+
+/obj/item/gun/ballistic/automatic/l6_saw/examine(mob/user)
+ . = ..()
+ . += "alt + click to [cover_open ? "close" : "open"] the dust cover."
+ if(cover_open && magazine)
+ . += "It seems like you could use an empty hand to remove the magazine."
+
+
+/obj/item/gun/ballistic/automatic/l6_saw/AltClick(mob/user)
+ . = ..()
+ if(!user.canUseTopic(src))
+ return
+ cover_open = !cover_open
+ to_chat(user, "You [cover_open ? "open" : "close"] [src]'s cover.")
+ playsound(src, 'sound/weapons/gun/l6/l6_door.ogg', 60, TRUE)
+ update_appearance()
+
+/obj/item/gun/ballistic/automatic/l6_saw/update_icon_state()
+ . = ..()
+ inhand_icon_state = "[base_icon_state][cover_open ? "open" : "closed"][magazine ? "mag":"nomag"]"
+
+/obj/item/gun/ballistic/automatic/l6_saw/update_overlays()
+ . = ..()
+ . += "l6_door_[cover_open ? "open" : "closed"]"
+
+
+/obj/item/gun/ballistic/automatic/l6_saw/afterattack(atom/target as mob|obj|turf, mob/living/user as mob|obj, flag, params)
+ if(cover_open)
+ to_chat(user, "[src]'s cover is open! Close it before firing!")
+ return
+ else
+ . = ..()
+ update_appearance()
+
+//ATTACK HAND IGNORING PARENT RETURN VALUE
+/obj/item/gun/ballistic/automatic/l6_saw/attack_hand(mob/user, list/modifiers)
+ if (loc != user)
+ ..()
+ return
+ if (!cover_open)
+ to_chat(user, "[src]'s cover is closed! Open it before trying to remove the magazine!")
+ return
+ ..()
+
+/obj/item/gun/ballistic/automatic/l6_saw/attackby(obj/item/A, mob/user, params)
+ if(!cover_open && istype(A, mag_type))
+ to_chat(user, "[src]'s dust cover prevents a magazine from being fit.")
+ return
+ ..()
+
+// SNIPER //
+
+/obj/item/gun/ballistic/automatic/sniper_rifle
+ name = "sniper rifle"
+ desc = "A long ranged weapon that does significant damage. No, you can't quickscope."
+ icon_state = "sniper"
+ w_class = WEIGHT_CLASS_BULKY
+ inhand_icon_state = "sniper"
+ worn_icon_state = null
+ fire_sound = 'sound/weapons/gun/sniper/shot.ogg'
+ fire_sound_volume = 90
+ vary_fire_sound = FALSE
+ load_sound = 'sound/weapons/gun/sniper/mag_insert.ogg'
+ rack_sound = 'sound/weapons/gun/sniper/rack.ogg'
+ suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg'
+ recoil = 2
+ weapon_weight = WEAPON_HEAVY
+ mag_type = /obj/item/ammo_box/magazine/sniper_rounds
+ fire_delay = 40
+ burst_size = 1
+ w_class = WEIGHT_CLASS_NORMAL
+ zoomable = TRUE
+ zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you.
+ zoom_out_amt = 5
+ slot_flags = ITEM_SLOT_BACK
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
+ mag_display = TRUE
+ suppressor_x_offset = 3
+ suppressor_y_offset = 3
+
+/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate
+ name = "syndicate sniper rifle"
+ desc = "An illegally modified .50 cal sniper rifle with suppression compatibility. Quickscoping still doesn't work."
+ can_suppress = TRUE
+ can_unsuppress = TRUE
+ pin = /obj/item/firing_pin/implant/pindicate
+
+// Old Semi-Auto Rifle //
+
+/obj/item/gun/ballistic/automatic/surplus
+ name = "Surplus Rifle"
+ desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm Magnum ammo and its bulky frame prevents one-hand firing."
+ icon_state = "surplus"
+ inhand_icon_state = "moistnugget"
+ worn_icon_state = null
+ weapon_weight = WEAPON_HEAVY
+ mag_type = /obj/item/ammo_box/magazine/m10mm/rifle
+ fire_delay = 30
+ burst_size = 1
+ can_unsuppress = TRUE
+ can_suppress = TRUE
+ w_class = WEIGHT_CLASS_HUGE
+ slot_flags = ITEM_SLOT_BACK
+ //actions_types = list() SKYRAT EDIT REMOVAL
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
+ mag_display = TRUE
+
+// Laser rifle (rechargeable magazine) //
+
+/obj/item/gun/ballistic/automatic/laser
+ name = "laser rifle"
+ desc = "Though sometimes mocked for the relatively weak firepower of their energy weapons, the logistic miracle of rechargeable ammunition has given Nanotrasen a decisive edge over many a foe."
+ icon_state = "oldrifle"
+ w_class = WEIGHT_CLASS_BULKY
+ inhand_icon_state = "arg"
+ mag_type = /obj/item/ammo_box/magazine/recharge
+ mag_display_ammo = TRUE
+ fire_delay = 2
+ can_suppress = FALSE
+ burst_size = 0
+ //actions_types = list() SKYRAT EDIT REMOVAL
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
+ fire_sound = 'sound/weapons/laser.ogg'
+ casing_ejector = FALSE
diff --git a/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm b/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm
new file mode 100644
index 00000000000..c23bafac9a4
--- /dev/null
+++ b/modular_skyrat/master_files/code/modules/projectiles/guns/gun.dm
@@ -0,0 +1,833 @@
+
+#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4
+#define FIRING_PIN_REMOVAL_DELAY 50
+
+/obj/item/gun
+ name = "gun"
+ desc = "It's a gun. It's pretty terrible, though."
+ icon = 'icons/obj/guns/ballistic.dmi'
+ icon_state = "detective"
+ inhand_icon_state = "gun"
+ worn_icon_state = "gun"
+ flags_1 = CONDUCT_1
+ slot_flags = ITEM_SLOT_BELT
+ custom_materials = list(/datum/material/iron=2000)
+ w_class = WEIGHT_CLASS_NORMAL
+ throwforce = 5
+ throw_speed = 3
+ throw_range = 5
+ force = 5
+ item_flags = NEEDS_PERMIT
+ attack_verb_continuous = list("strikes", "hits", "bashes")
+ attack_verb_simple = list("strike", "hit", "bash")
+
+ var/gun_flags = NONE
+ var/fire_sound = 'sound/weapons/gun/pistol/shot.ogg'
+ var/vary_fire_sound = TRUE
+ var/fire_sound_volume = 50
+ var/dry_fire_sound = 'sound/weapons/gun/general/dry_fire.ogg'
+ var/suppressed = null //whether or not a message is displayed when fired
+ var/can_suppress = FALSE
+ var/suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg'
+ var/suppressed_volume = 60
+ var/can_unsuppress = TRUE
+ var/recoil = 0 //boom boom shake the room
+ var/clumsy_check = TRUE
+ var/obj/item/ammo_casing/chambered = null
+ trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers
+ var/sawn_desc = null //description change if weapon is sawn-off
+ var/sawn_off = FALSE
+ var/burst_size = 1 //how large a burst is
+ var/fire_delay = 0 //rate of fire for burst firing and semi auto
+ var/firing_burst = 0 //Prevent the weapon from firing again while already firing
+ var/semicd = 0 //cooldown handler
+ var/weapon_weight = WEAPON_LIGHT
+ var/dual_wield_spread = 24 //additional spread when dual wielding
+
+ /// Just 'slightly' snowflakey way to modify projectile damage for projectiles fired from this gun.
+ var/projectile_damage_multiplier = 1
+
+ var/spread = 0 //Spread induced by the gun itself.
+ var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once.
+
+ lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
+
+ var/obj/item/firing_pin/pin = /obj/item/firing_pin //standard firing pin for most guns
+
+ var/can_flashlight = FALSE //if a flashlight can be added or removed if it already has one.
+ var/obj/item/flashlight/seclite/gun_light
+ var/datum/action/item_action/toggle_gunlight/alight
+ var/gunlight_state = "flight"
+
+ var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one.
+ var/obj/item/kitchen/knife/bayonet
+ var/knife_x_offset = 0
+ var/knife_y_offset = 0
+
+ var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite
+ var/ammo_y_offset = 0
+ var/flight_x_offset = 0
+ var/flight_y_offset = 0
+
+ //Zooming
+ var/zoomable = FALSE //whether the gun generates a Zoom action on creation
+ var/zoomed = FALSE //Zoom toggle
+ var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect)
+ var/zoom_out_amt = 0
+ var/datum/action/toggle_scope_zoom/azoom
+ var/pb_knockback = 0
+
+ var/safety = FALSE ///Internal variable for keeping track whether the safety is on or off
+ var/has_gun_safety = FALSE ///Whether the gun actually has a gun safety
+ var/datum/action/item_action/toggle_safety/tsafety
+
+ var/datum/action/item_action/toggle_firemode/firemode_action
+ ///Current fire selection, can choose between burst, single, and full auto.
+ var/fire_select = SELECT_SEMI_AUTOMATIC
+ var/fire_select_index = 1
+ ///What modes does this weapon have? Put SELECT_FULLY_AUTOMATIC in here to enable fully automatic behaviours.
+ var/list/fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
+ ///if i`1t has an icon for a selector switch indicating current firemode.
+ var/selector_switch_icon = FALSE
+
+/datum/action/item_action/toggle_safety
+ name = "Toggle Safety"
+ icon_icon = 'modular_skyrat/modules/gunsafety/icons/hud/actions.dmi'
+ button_icon_state = "safety_on"
+
+/obj/item/gun/ui_action_click(mob/user, actiontype)
+ if(istype(actiontype, /datum/action/item_action/toggle_firemode))
+ fire_select()
+ else if(istype(actiontype, tsafety))
+ toggle_safety(user)
+ else
+ ..()
+
+/obj/item/gun/Initialize()
+ . = ..()
+ if(pin)
+ pin = new pin(src)
+
+ if(gun_light)
+ alight = new(src)
+
+ build_zooming()
+
+ if(has_gun_safety)
+ safety = TRUE
+ tsafety = new(src)
+
+ if(burst_size > 1 && !(SELECT_BURST_SHOT in fire_select_modes))
+ fire_select_modes.Add(SELECT_BURST_SHOT)
+ else if(burst_size <= 1 && (SELECT_BURST_SHOT in fire_select_modes))
+ fire_select_modes.Remove(SELECT_BURST_SHOT)
+
+ burst_size = 1
+
+ sortList(fire_select_modes, /proc/cmp_numeric_asc)
+
+ if(fire_select_modes.len > 1)
+ firemode_action = new(src)
+ firemode_action.button_icon_state = "fireselect_[fire_select]"
+ firemode_action.UpdateButtonIcon()
+
+/obj/item/gun/ComponentInitialize()
+ . = ..()
+ if(SELECT_FULLY_AUTOMATIC in fire_select_modes)
+ AddComponent(/datum/component/automatic_fire, fire_delay)
+
+/obj/item/gun/Destroy()
+ if(isobj(pin)) //Can still be the initial path, then we skip
+ QDEL_NULL(pin)
+ if(gun_light)
+ QDEL_NULL(gun_light)
+ if(bayonet)
+ QDEL_NULL(bayonet)
+ if(chambered) //Not all guns are chambered (EMP'ed energy guns etc)
+ QDEL_NULL(chambered)
+ if(azoom)
+ QDEL_NULL(azoom)
+ if(suppressed)
+ QDEL_NULL(suppressed)
+ if(tsafety)
+ QDEL_NULL(tsafety)
+ if(firemode_action)
+ QDEL_NULL(firemode_action)
+ . = ..()
+
+/obj/item/gun/handle_atom_del(atom/A)
+ if(A == pin)
+ pin = null
+ if(A == chambered)
+ chambered = null
+ update_appearance()
+ if(A == bayonet)
+ clear_bayonet()
+ if(A == gun_light)
+ clear_gunlight()
+ if(A == suppressed)
+ clear_suppressor()
+ . = ..()
+
+///Clears var and updates icon. In the case of ballistic weapons, also updates the gun's weight.
+/obj/item/gun/proc/clear_suppressor()
+ if(!can_unsuppress)
+ return
+ suppressed = null
+ update_appearance()
+
+/obj/item/gun/examine(mob/user)
+ . = ..()
+ if(pin)
+ . += "It has \a [pin] installed."
+ . += "[pin] looks like it could be removed with some tools."
+ else
+ . += "It doesn't have a firing pin installed, and won't fire."
+
+ if(gun_light)
+ . += "It has \a [gun_light] [can_flashlight ? "" : "permanently "]mounted on it."
+ if(can_flashlight) //if it has a light and this is false, the light is permanent.
+ . += "[gun_light] looks like it can be unscrewed from [src]."
+ else if(can_flashlight)
+ . += "It has a mounting point for a seclite."
+
+ if(bayonet)
+ . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it."
+ if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent.
+ . += "[bayonet] looks like it can be unscrewed from [src]."
+ if(can_bayonet)
+ . += "It has a bayonet lug on it."
+ if(has_gun_safety)
+ . += "The safety is [safety ? "ON" : "OFF"]."
+
+/obj/item/gun/equipped(mob/living/user, slot)
+ . = ..()
+ if(zoomed && user.get_active_held_item() != src)
+ zoom(user, user.dir, FALSE) //we can only stay zoomed in if it's in our hands //yeah and we only unzoom if we're actually zoomed using the gun!!
+
+/obj/item/gun/proc/fire_select()
+ var/mob/living/carbon/human/user = usr
+
+ var/max_mode = fire_select_modes.len
+
+ if(max_mode <= 1)
+ to_chat(user, "[src] is not capable of switching firemodes!")
+ return
+
+ fire_select_index = 1 + fire_select_index % max_mode //Magic math to cycle through this shit!
+
+ fire_select = fire_select_modes[fire_select_index]
+
+ switch(fire_select)
+ if(SELECT_SEMI_AUTOMATIC)
+ burst_size = 1
+ fire_delay = 0
+ SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user)
+ to_chat(user, "You switch [src] to semi-automatic.")
+ if(SELECT_BURST_SHOT)
+ burst_size = initial(burst_size)
+ fire_delay = initial(fire_delay)
+ SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user)
+ to_chat(user, "You switch [src] to [burst_size]-round burst.")
+ if(SELECT_FULLY_AUTOMATIC)
+ burst_size = 1
+ SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_SELECTED, user)
+ to_chat(user, "You switch [src] to automatic.")
+
+ playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
+ update_appearance()
+ firemode_action.button_icon_state = "fireselect_[fire_select]"
+ firemode_action.UpdateButtonIcon()
+ return TRUE
+
+//called after the gun has successfully fired its chambered ammo.
+/obj/item/gun/proc/process_chamber()
+ return FALSE
+
+//check if there's enough ammo/energy/whatever to shoot one time
+//i.e if clicking would make it shoot
+/obj/item/gun/proc/can_shoot()
+ return TRUE
+
+/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj)
+ to_chat(user, "*click*")
+ playsound(src, dry_fire_sound, 30, TRUE)
+
+
+/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
+ if(recoil)
+ shake_camera(user, recoil + 1, recoil)
+
+ if(suppressed)
+ playsound(user, suppressed_sound, suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0)
+ else
+ playsound(user, fire_sound, fire_sound_volume, vary_fire_sound)
+ if(message)
+ if(pointblank)
+ user.visible_message("[user] fires [src] point blank at [pbtarget]!", \
+ "You fire [src] point blank at [pbtarget]!", \
+ "You hear a gunshot!", COMBAT_MESSAGE_RANGE, pbtarget)
+ to_chat(pbtarget, "[user] fires [src] point blank at you!")
+ if(pb_knockback > 0 && ismob(pbtarget))
+ var/mob/PBT = pbtarget
+ var/atom/throw_target = get_edge_target_turf(PBT, user.dir)
+ PBT.throw_at(throw_target, pb_knockback, 2)
+ else
+ user.visible_message("[user] fires [src]!", \
+ "You fire [src]!", \
+ "You hear a gunshot!", COMBAT_MESSAGE_RANGE)
+
+/obj/item/gun/emp_act(severity)
+ . = ..()
+ if(!(. & EMP_PROTECT_CONTENTS))
+ for(var/obj/O in contents)
+ O.emp_act(severity)
+
+/obj/item/gun/attack_secondary(mob/living/victim, mob/living/user, params)
+ if(user.GetComponent(/datum/component/gunpoint))
+ to_chat(user, "You are already holding someone up!")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ if(user == victim)
+ to_chat(user,"You can't hold yourself up!")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ user.AddComponent(/datum/component/gunpoint, victim, src)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params)
+ . = ..()
+ if(QDELETED(target))
+ return
+ if(firing_burst)
+ return
+ if(flag) //It's adjacent, is the user, or is on the user's person
+ if(target in user.contents) //can't shoot stuff inside us.
+ return
+ if(!ismob(target) || user.combat_mode) //melee attack
+ return
+ if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected)
+ return
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ for(var/i in C.all_wounds)
+ var/datum/wound/W = i
+ if(W.try_treating(src, user))
+ return // another coward cured!
+
+ if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can.
+ var/mob/living/L = user
+ if(!can_trigger_gun(L))
+ return
+ if(flag)
+ if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH)
+ handle_suicide(user, target, params)
+ return
+
+ if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can shoot.
+ shoot_with_empty_chamber(user)
+ return
+
+ if(check_botched(user))
+ return
+
+ var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands
+ if(weapon_weight == WEAPON_HEAVY && (user.get_inactive_held_item() || !other_hand))
+ to_chat(user, "You need two hands to fire [src]!")
+ return
+ //DUAL (or more!) WIELDING
+ var/bonus_spread = 0
+ var/loop_counter = 0
+ if(ishuman(user) && user.combat_mode)
+ var/mob/living/carbon/human/H = user
+ for(var/obj/item/gun/G in H.held_items)
+ if(G == src || G.weapon_weight >= WEAPON_MEDIUM)
+ continue
+ else if(G.can_trigger_gun(user))
+ bonus_spread += dual_wield_spread
+ loop_counter++
+ addtimer(CALLBACK(G, /obj/item/gun.proc/process_fire, target, user, TRUE, params, null, bonus_spread), loop_counter)
+
+ return process_fire(target, user, TRUE, params, null, bonus_spread)
+
+/obj/item/gun/proc/check_botched(mob/living/user, params)
+ if(clumsy_check)
+ if(istype(user))
+ if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
+ to_chat(user, "You shoot yourself in the foot with [src]!")
+ var/shot_leg = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ process_fire(user, user, FALSE, params, shot_leg)
+ SEND_SIGNAL(user, COMSIG_MOB_CLUMSY_SHOOT_FOOT)
+ user.dropItemToGround(src, TRUE)
+ return TRUE
+
+/obj/item/gun/can_trigger_gun(mob/living/user)
+ . = ..()
+ if(!handle_pins(user))
+ return FALSE
+ if(has_gun_safety && safety)
+ to_chat(user, "The safety is on!")
+ return FALSE
+
+/obj/item/gun/proc/toggle_safety(mob/user, override)
+ if(!has_gun_safety)
+ return
+ if(override)
+ if(override == "off")
+ safety = FALSE
+ else
+ safety = TRUE
+ else
+ safety = !safety
+ tsafety.button_icon_state = "safety_[safety ? "on" : "off"]"
+ tsafety.UpdateButtonIcon()
+ playsound(src, 'sound/weapons/empty.ogg', 100, TRUE)
+ user.visible_message("[user] toggles [src]'s safety [safety ? "ON" : "OFF"].",
+ "You toggle [src]'s safety [safety ? "ON" : "OFF"].")
+
+/obj/item/gun/proc/handle_pins(mob/living/user)
+ if(pin)
+ if(pin.pin_auth(user) || (pin.obj_flags & EMAGGED))
+ return TRUE
+ else
+ pin.auth_fail(user)
+ return FALSE
+ else
+ to_chat(user, "[src]'s trigger is locked. This weapon doesn't have a firing pin installed!")
+ return FALSE
+
+/obj/item/gun/proc/recharge_newshot()
+ return
+
+/obj/item/gun/proc/process_burst(mob/living/user, atom/target, message = TRUE, params=null, zone_override = "", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0)
+ if(!user || !firing_burst)
+ firing_burst = FALSE
+ return FALSE
+ if(!issilicon(user))
+ if(iteration > 1 && !(user.is_holding(src))) //for burst firing
+ firing_burst = FALSE
+ return FALSE
+ if(chambered?.loaded_projectile)
+ if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal.
+ if(chambered.harmful) // Is the bullet chambered harmful?
+ to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...")
+ return
+ if(randomspread)
+ sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
+ else //Smart spread
+ sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread))
+ before_firing(target,user)
+ if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd, src))
+ shoot_with_empty_chamber(user)
+ firing_burst = FALSE
+ return FALSE
+ else
+ if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot
+ shoot_live_shot(user, 1, target, message)
+ else
+ shoot_live_shot(user, 0, target, message)
+ if (iteration >= burst_size)
+ firing_burst = FALSE
+ else
+ shoot_with_empty_chamber(user)
+ firing_burst = FALSE
+ return FALSE
+ process_chamber()
+ update_appearance()
+ return TRUE
+
+/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ if(user)
+ if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex
+ target = pick(orange(2, target))
+ SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override)
+
+ SEND_SIGNAL(src, COMSIG_GUN_FIRED, user, target, params, zone_override)
+
+ add_fingerprint(user)
+
+ if(semicd)
+ return
+
+ var/sprd = 0
+ var/randomized_gun_spread = 0
+ var/rand_spr = rand()
+ if(spread)
+ randomized_gun_spread = rand(0,spread)
+ var/randomized_bonus_spread = rand(0, bonus_spread)
+
+ if(burst_size > 1)
+ firing_burst = TRUE
+ for(var/i = 1 to burst_size)
+ addtimer(CALLBACK(src, .proc/process_burst, user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i), fire_delay * (i - 1))
+ else
+ if(chambered)
+ if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal.
+ if(chambered.harmful) // Is the bullet chambered harmful?
+ to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...")
+ return
+ sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
+ before_firing(target,user)
+ if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src))
+ shoot_with_empty_chamber(user)
+ return
+ else
+ if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot
+ shoot_live_shot(user, 1, target, message)
+ else
+ shoot_live_shot(user, 0, target, message)
+ else
+ shoot_with_empty_chamber(user)
+ return
+ process_chamber()
+ update_appearance()
+ semicd = TRUE
+ addtimer(CALLBACK(src, .proc/reset_semicd), fire_delay)
+
+ if(user)
+ user.update_inv_hands()
+ SSblackbox.record_feedback("tally", "gun_fired", 1, type)
+
+ return TRUE
+
+/obj/item/gun/proc/reset_semicd()
+ semicd = FALSE
+
+/obj/item/gun/attack(mob/M, mob/living/user)
+ if(user.combat_mode) //Flogging
+ if(bayonet)
+ M.attackby(bayonet, user)
+ return
+ else
+ return ..()
+ return
+
+/obj/item/gun/attack_obj(obj/O, mob/living/user, params)
+ if(user.combat_mode)
+ if(bayonet)
+ O.attackby(bayonet, user)
+ return
+ return ..()
+
+/obj/item/gun/attackby(obj/item/I, mob/living/user, params)
+ if(user.combat_mode)
+ return ..()
+ else if(istype(I, /obj/item/flashlight/seclite))
+ if(!can_flashlight)
+ return ..()
+ var/obj/item/flashlight/seclite/S = I
+ if(!gun_light)
+ if(!user.transferItemToLoc(I, src))
+ return
+ to_chat(user, "You click [S] into place on [src].")
+ set_gun_light(S)
+ update_gunlight()
+ alight = new(src)
+ if(loc == user)
+ alight.Grant(user)
+ else if(istype(I, /obj/item/kitchen/knife))
+ var/obj/item/kitchen/knife/K = I
+ if(!can_bayonet || !K.bayonet || bayonet) //ensure the gun has an attachment point available, and that the knife is compatible with it.
+ return ..()
+ if(!user.transferItemToLoc(I, src))
+ return
+ to_chat(user, "You attach [K] to [src]'s bayonet lug.")
+ bayonet = K
+ update_appearance()
+
+ else
+ return ..()
+
+/obj/item/gun/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(.)
+ return
+ if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ if((can_flashlight && gun_light) && (can_bayonet && bayonet)) //give them a choice instead of removing both
+ var/list/possible_items = list(gun_light, bayonet)
+ var/obj/item/item_to_remove = input(user, "Select an attachment to remove", "Attachment Removal") as null|obj in sortNames(possible_items)
+ if(!item_to_remove || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ return remove_gun_attachment(user, I, item_to_remove)
+
+ else if(gun_light && can_flashlight) //if it has a gun_light and can_flashlight is false, the flashlight is permanently attached.
+ return remove_gun_attachment(user, I, gun_light, "unscrewed")
+
+ else if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed
+ return remove_gun_attachment(user, I, bayonet, "unfix")
+
+ else if(pin && user.is_holding(src))
+ user.visible_message("[user] attempts to remove [pin] from [src] with [I].",
+ "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3)
+ if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50))
+ if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay
+ return
+ user.visible_message("[pin] is pried out of [src] by [user], destroying the pin in the process.",
+ "You pry [pin] out with [I], destroying the pin in the process.", null, 3)
+ QDEL_NULL(pin)
+ return TRUE
+
+/obj/item/gun/welder_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(.)
+ return
+ if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ if(pin && user.is_holding(src))
+ user.visible_message("[user] attempts to remove [pin] from [src] with [I].",
+ "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3)
+ if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, 5, volume = 50))
+ if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay
+ return
+ user.visible_message("[pin] is spliced out of [src] by [user], melting part of the pin in the process.",
+ "You splice [pin] out of [src] with [I], melting part of the pin in the process.", null, 3)
+ QDEL_NULL(pin)
+ return TRUE
+
+/obj/item/gun/wirecutter_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(.)
+ return
+ if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ if(pin && user.is_holding(src))
+ user.visible_message("[user] attempts to remove [pin] from [src] with [I].",
+ "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3)
+ if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50))
+ if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay
+ return
+ user.visible_message("[pin] is ripped out of [src] by [user], mangling the pin in the process.",
+ "You rip [pin] out of [src] with [I], mangling the pin in the process.", null, 3)
+ QDEL_NULL(pin)
+ return TRUE
+
+/obj/item/gun/proc/remove_gun_attachment(mob/living/user, obj/item/tool_item, obj/item/item_to_remove, removal_verb)
+ if(tool_item)
+ tool_item.play_tool_sound(src)
+ to_chat(user, "You [removal_verb ? removal_verb : "remove"] [item_to_remove] from [src].")
+ item_to_remove.forceMove(drop_location())
+
+ if(Adjacent(user) && !issilicon(user))
+ user.put_in_hands(item_to_remove)
+
+ if(item_to_remove == bayonet)
+ return clear_bayonet()
+ else if(item_to_remove == gun_light)
+ return clear_gunlight()
+
+/obj/item/gun/proc/clear_bayonet()
+ if(!bayonet)
+ return
+ bayonet = null
+ update_appearance()
+ return TRUE
+
+/obj/item/gun/proc/clear_gunlight()
+ if(!gun_light)
+ return
+ var/obj/item/flashlight/seclite/removed_light = gun_light
+ set_gun_light(null)
+ update_gunlight()
+ removed_light.update_brightness()
+ QDEL_NULL(alight)
+ return TRUE
+
+
+/**
+ * Swaps the gun's seclight, dropping the old seclight if it has not been qdel'd.
+ *
+ * Returns the former gun_light that has now been replaced by this proc.
+ * Arguments:
+ * * new_light - The new light to attach to the weapon. Can be null, which will mean the old light is removed with no replacement.
+ */
+/obj/item/gun/proc/set_gun_light(obj/item/flashlight/seclite/new_light)
+ // Doesn't look like this should ever happen? We're replacing our old light with our old light?
+ if(gun_light == new_light)
+ CRASH("Tried to set a new gun light when the old gun light was also the new gun light.")
+
+ . = gun_light
+
+ // If there's an old gun light that isn't being QDELETED, detatch and drop it to the floor.
+ if(!QDELETED(gun_light))
+ gun_light.set_light_flags(gun_light.light_flags & ~LIGHT_ATTACHED)
+ if(gun_light.loc == src)
+ gun_light.forceMove(get_turf(src))
+
+ // If there's a new gun light to be added, attach and move it to the gun.
+ if(new_light)
+ new_light.set_light_flags(new_light.light_flags | LIGHT_ATTACHED)
+ if(new_light.loc != src)
+ new_light.forceMove(src)
+
+ gun_light = new_light
+
+/obj/item/gun/ui_action_click(mob/user, actiontype)
+ if(istype(actiontype, alight))
+ toggle_gunlight()
+ else
+ ..()
+
+/obj/item/gun/proc/toggle_gunlight()
+ if(!gun_light)
+ return
+
+ var/mob/living/carbon/human/user = usr
+ gun_light.on = !gun_light.on
+ gun_light.update_brightness()
+ to_chat(user, "You toggle the gunlight [gun_light.on ? "on":"off"].")
+
+ playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
+ update_gunlight()
+
+/obj/item/gun/proc/update_gunlight()
+ update_appearance()
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.UpdateButtonIcon()
+
+/obj/item/gun/pickup(mob/user)
+ . = ..()
+ if(azoom)
+ azoom.Grant(user)
+ if(w_class > WEIGHT_CLASS_SMALL && !suppressed)
+ user.visible_message("[user] grabs [src]!",
+ "You grab [src]!")
+
+/obj/item/gun/dropped(mob/user)
+ . = ..()
+ if(azoom)
+ azoom.Remove(user)
+ if(zoomed)
+ zoom(user, user.dir)
+
+/obj/item/gun/update_overlays()
+ . = ..()
+ if(gun_light)
+ var/mutable_appearance/flashlight_overlay
+ var/state = "[gunlight_state][gun_light.on? "_on":""]" //Generic state.
+ if(gun_light.icon_state in icon_states('icons/obj/guns/flashlights.dmi')) //Snowflake state?
+ state = gun_light.icon_state
+ flashlight_overlay = mutable_appearance('icons/obj/guns/flashlights.dmi', state)
+ flashlight_overlay.pixel_x = flight_x_offset
+ flashlight_overlay.pixel_y = flight_y_offset
+ . += flashlight_overlay
+
+ if(bayonet)
+ var/mutable_appearance/knife_overlay
+ var/state = "bayonet" //Generic state.
+ if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state?
+ state = bayonet.icon_state
+ var/icon/bayonet_icons = 'icons/obj/guns/bayonets.dmi'
+ knife_overlay = mutable_appearance(bayonet_icons, state)
+ knife_overlay.pixel_x = knife_x_offset
+ knife_overlay.pixel_y = knife_y_offset
+ . += knife_overlay
+
+/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer)
+ if(!ishuman(user) || !ishuman(target))
+ return
+
+ if(semicd)
+ return
+
+ if(user == target)
+ target.visible_message("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...", \
+ "You stick [src] in your mouth, ready to pull the trigger...")
+ else
+ target.visible_message("[user] points [src] at [target]'s head, ready to pull the trigger...", \
+ "[user] points [src] at your head, ready to pull the trigger...")
+
+ semicd = TRUE
+
+ if(!bypass_timer && (!do_mob(user, target, 120) || user.zone_selected != BODY_ZONE_PRECISE_MOUTH))
+ if(user)
+ if(user == target)
+ user.visible_message("[user] decided not to shoot.")
+ else if(target?.Adjacent(user))
+ target.visible_message("[user] has decided to spare [target]", "[user] has decided to spare your life!")
+ semicd = FALSE
+ return
+
+ semicd = FALSE
+
+ target.visible_message("[user] pulls the trigger!", "[(user == target) ? "You pull" : "[user] pulls"] the trigger!")
+
+ if(chambered?.loaded_projectile)
+ chambered.loaded_projectile.damage *= 5
+ if(chambered.loaded_projectile.wound_bonus != CANT_WOUND)
+ chambered.loaded_projectile.wound_bonus += 5 // much more dramatic on multiple pellet'd projectiles really
+
+ var/fired = process_fire(target, user, TRUE, params, BODY_ZONE_HEAD)
+ if(!fired && chambered?.loaded_projectile)
+ chambered.loaded_projectile.damage /= 5
+ if(chambered.loaded_projectile.wound_bonus != CANT_WOUND)
+ chambered.loaded_projectile.wound_bonus -= 5
+
+/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins
+ if(pin)
+ qdel(pin)
+ pin = new /obj/item/firing_pin
+
+//Happens before the actual projectile creation
+/obj/item/gun/proc/before_firing(atom/target,mob/user)
+ return
+
+/////////////
+// ZOOMING //
+/////////////
+
+/datum/action/toggle_scope_zoom
+ name = "Toggle Scope"
+ check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_LYING
+ icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ var/obj/item/gun/gun = null
+
+/datum/action/toggle_scope_zoom/Trigger()
+ gun.zoom(owner, owner.dir)
+
+/datum/action/toggle_scope_zoom/IsAvailable()
+ . = ..()
+ if(!. && gun)
+ gun.zoom(owner, owner.dir, FALSE)
+
+/datum/action/toggle_scope_zoom/Remove(mob/living/L)
+ gun.zoom(L, L.dir, FALSE)
+ ..()
+
+/obj/item/gun/proc/rotate(atom/thing, old_dir, new_dir)
+ SIGNAL_HANDLER
+
+ if(ismob(thing))
+ var/mob/lad = thing
+ lad.client.view_size.zoomOut(zoom_out_amt, zoom_amt, new_dir)
+
+/obj/item/gun/proc/zoom(mob/living/user, direc, forced_zoom)
+ if(!user || !user.client)
+ return
+
+ if(isnull(forced_zoom))
+ zoomed = !zoomed
+ else
+ zoomed = forced_zoom
+
+ if(zoomed)
+ RegisterSignal(user, COMSIG_ATOM_DIR_CHANGE, .proc/rotate)
+ user.client.view_size.zoomOut(zoom_out_amt, zoom_amt, direc)
+ else
+ UnregisterSignal(user, COMSIG_ATOM_DIR_CHANGE)
+ user.client.view_size.zoomIn()
+ return zoomed
+
+//Proc, so that gun accessories/scopes/etc. can easily add zooming.
+/obj/item/gun/proc/build_zooming()
+ if(azoom)
+ return
+
+ if(zoomable)
+ azoom = new()
+ azoom.gun = src
+
+#undef FIRING_PIN_REMOVAL_DELAY
+#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER
diff --git a/modular_skyrat/master_files/icons/mob/actions/actions_items.dmi b/modular_skyrat/master_files/icons/mob/actions/actions_items.dmi
new file mode 100644
index 00000000000..115d5d71dad
Binary files /dev/null and b/modular_skyrat/master_files/icons/mob/actions/actions_items.dmi differ
diff --git a/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm b/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm
index 9051036723e..4693970a072 100644
--- a/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm
+++ b/modular_skyrat/modules/ERT_Factions/FTU/code/items.dm
@@ -48,7 +48,6 @@
can_suppress = FALSE
burst_size = 5
spread = 3
- actions_types = list(/datum/action/item_action/toggle_firemode)
mag_display = TRUE
mag_display_ammo = TRUE
realistic = TRUE
@@ -86,7 +85,6 @@
can_suppress = FALSE
burst_size = 8
spread = 5
- actions_types = list(/datum/action/item_action/toggle_firemode)
mag_display = TRUE
mag_display_ammo = FALSE
realistic = TRUE
diff --git a/modular_skyrat/modules/aesthetics/guns/code/guns.dm b/modular_skyrat/modules/aesthetics/guns/code/guns.dm
index 964843550b9..18f2a14471a 100644
--- a/modular_skyrat/modules/aesthetics/guns/code/guns.dm
+++ b/modular_skyrat/modules/aesthetics/guns/code/guns.dm
@@ -142,12 +142,12 @@
/obj/item/ammo_casing/energy/laser/accelerator
fire_sound = 'modular_skyrat/modules/aesthetics/guns/sound/laser_cannon_fire.ogg'
-
+
/obj/item/gun/ballistic/automatic/sniper_rifle
icon = 'modular_skyrat/modules/aesthetics/guns/icons/guns_gubman2.dmi'
icon_state = "sniper"
fire_delay = 60
-
+
/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate
icon = 'modular_skyrat/modules/aesthetics/guns/icons/guns_gubman2.dmi'
icon_state = "sniper2"
@@ -198,7 +198,7 @@
zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you.
zoom_out_amt = 5
slot_flags = ITEM_SLOT_BACK
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE
mag_display = TRUE
/obj/item/gun/ballistic/automatic/ar/modular
@@ -223,6 +223,6 @@
w_class = WEIGHT_CLASS_NORMAL
can_suppress = FALSE
weapon_weight = WEAPON_HEAVY
-
+
/obj/item/gun/energy/kinetic_accelerator
icon = 'modular_skyrat/modules/aesthetics/guns/icons/energy.dmi'
diff --git a/modular_skyrat/modules/gunsafety/code/gun_safety.dm b/modular_skyrat/modules/gunsafety/code/gun_safety.dm
index 0e6cf38d1df..afd73605369 100644
--- a/modular_skyrat/modules/gunsafety/code/gun_safety.dm
+++ b/modular_skyrat/modules/gunsafety/code/gun_safety.dm
@@ -1,58 +1,3 @@
-//WATCH YOUR TRIGGER FINGER FRANK
-/obj/item/gun
- var/safety = FALSE //Internal variable for keeping track whether the safety is on or off
- var/has_gun_safety = FALSE //Whether the gun actually has a gun safety
- var/datum/action/item_action/toggle_safety/tsafety
-
-/obj/item/gun/Initialize()
- . = ..()
- if(has_gun_safety)
- safety = TRUE
- tsafety = new(src)
-
-/obj/item/gun/Destroy()
- if(tsafety)
- QDEL_NULL(tsafety)
- return ..()
-
-/datum/action/item_action/toggle_safety
- name = "Toggle Safety"
- icon_icon = 'modular_skyrat/modules/gunsafety/icons/hud/actions.dmi'
- button_icon_state = "safety_on"
-
-/obj/item/gun/ui_action_click(mob/user, actiontype)
- if(istype(actiontype, tsafety))
- toggle_safety(user)
- else
- ..()
-
-/obj/item/gun/proc/toggle_safety(mob/user, override)
- if(!has_gun_safety)
- return
- if(override)
- if(override == "off")
- safety = FALSE
- else
- safety = TRUE
- else
- safety = !safety
- tsafety.button_icon_state = "safety_[safety ? "on" : "off"]"
- tsafety.UpdateButtonIcon()
- playsound(src, 'sound/weapons/empty.ogg', 100, TRUE)
- user.visible_message("[user] toggles [src]'s safety [safety ? "ON" : "OFF"].",
- "You toggle [src]'s safety [safety ? "ON" : "OFF"].")
-
-/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params)
- if(has_gun_safety)
- if(safety)
- to_chat(user, "The safety is on!")
- return
- . = ..()
-
-/obj/item/gun/examine(mob/user)
- . = ..()
- . += "The safety is [safety ? "ON" : "OFF"]."
-
/obj/item/gun/ballistic
has_gun_safety = TRUE
@@ -64,6 +9,6 @@
/obj/item/gun/energy/kinetic_accelerator/minebot
has_gun_safety = FALSE
-
+
/obj/item/gun/ballistic/rifle/enchanted
has_gun_safety = FALSE
diff --git a/modular_skyrat/modules/gunsgalore/code/guns/master.dm b/modular_skyrat/modules/gunsgalore/code/guns/master.dm
index a05e331dbde..d08239e5389 100644
--- a/modular_skyrat/modules/gunsgalore/code/guns/master.dm
+++ b/modular_skyrat/modules/gunsgalore/code/guns/master.dm
@@ -11,12 +11,19 @@
var/durability = 100 //How used this gun is.
var/durability_factor = 0.1 //How quickly a gun will degrade. 0.1 = 1000 shots. Edit this to change a guns base reliability.
+
/obj/item/gun/ballistic/Initialize()
. = ..()
if(realistic)
base_spread = spread
+/obj/item/gun/ballistic/ComponentInitialize()
+ . = ..()
+ if(alt_icons)
+ AddElement(/datum/element/update_icon_updates_onmob)
+
/obj/item/gun/ballistic/update_overlays()
+ . = ..()
if(alt_icons)
if(!magazine)
if(alt_icon_state)
@@ -32,14 +39,6 @@
else
inhand_icon_state = "[initial(icon_state)]"
worn_icon_state = "[initial(icon_state)]"
- . = ..()
-
-//gun pickup message
-/obj/item/gun/pickup(mob/user)
- . = ..()
- if(w_class > WEIGHT_CLASS_SMALL && !suppressed)
- user.visible_message("[user] grabs [src]!",
- "You grab [src]!")
/obj/item/gun/ballistic/assault_rifle
rack_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/ltrifle_cock.ogg'
@@ -76,11 +75,6 @@
eject_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magout.ogg'
eject_empty_sound = 'modular_skyrat/modules/gunsgalore/sound/guns/interact/smg_magout.ogg'
-/obj/item/gun/ballistic/ComponentInitialize()
- if(alt_icons)
- AddElement(/datum/element/update_icon_updates_onmob)
- . = ..()
-
/obj/item/gun/ballistic/proc/jam(unjam = FALSE, mob/living/user)
if(unjam && jammed != TRUE)
unjam_time = clamp((jam_chance*10)/(durability/10), 0, 50)
@@ -94,31 +88,11 @@
to_chat(user, "You unjam the [src]'s bolt.")
playsound(src, 'sound/weapons/gun/l6/l6_rack.ogg', 60, TRUE)
-/obj/item/gun/ballistic/can_shoot()
- if(realistic)
- if(jammed)
- return FALSE
- else
- . = ..()
- else
- . = ..()
-
-/obj/item/gun/ballistic/process_burst(mob/living/user, atom/target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, iteration)
- if(realistic)
- if(jammed)
- firing_burst = FALSE
- return FALSE
- else
- . = ..()
- else
- . = ..()
-
-/obj/item/gun/ballistic/process_fire()
- if(realistic)
- if(jammed)
- shoot_with_empty_chamber()
- return
+/obj/item/gun/ballistic/can_trigger_gun(mob/living/user)
. = ..()
+ if(realistic && jammed)
+ to_chat(user, "[src] is jammed!")
+ return FALSE
/obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message)
if(realistic)
@@ -161,17 +135,6 @@
return
. = ..()
-/obj/item/gun/ballistic/afterattack(atom/target as mob|obj|turf, mob/living/user as mob|obj, flag, params)
- if(realistic)
- if(jammed)
- to_chat(user, "[src] is jammed, unjam it before firing!")
- return
- else
- . = ..()
- update_icon()
- else
- . = ..()
-
/obj/item/gun/ballistic/examine(mob/user)
. = ..()
if(realistic)
diff --git a/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm b/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm
index 26968c41cca..df448564a23 100644
--- a/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm
+++ b/modular_skyrat/modules/gunsgalore/code/guns/stg/stg.dm
@@ -17,6 +17,7 @@
fire_sound_volume = 70
alt_icons = TRUE
realistic = TRUE
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT, SELECT_FULLY_AUTOMATIC)
/obj/item/ammo_box/magazine/stg
name = "stg magazine (7.92×33mm)"
diff --git a/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm b/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm
index 065ea423fdd..a74a718d09b 100644
--- a/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm
+++ b/modular_skyrat/modules/modular_weapons/code/modules/projectiles/guns/ballistic/rifle.dm
@@ -132,7 +132,7 @@
fire_delay = 5
can_suppress = FALSE
burst_size = 0
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC) //SKYRAT EDIT CHANGE
mag_display = FALSE
mag_display_ammo = FALSE
empty_indicator = FALSE
diff --git a/modular_skyrat/modules/sec_haul/code/guns/guns.dm b/modular_skyrat/modules/sec_haul/code/guns/guns.dm
index 15b968f8ade..b49ea33c377 100644
--- a/modular_skyrat/modules/sec_haul/code/guns/guns.dm
+++ b/modular_skyrat/modules/sec_haul/code/guns/guns.dm
@@ -73,7 +73,6 @@
burst_size = 3
fire_delay = 1
spread = 10
- actions_types = list(/datum/action/item_action/toggle_firemode)
realistic = TRUE
mag_display = FALSE
mag_display_ammo = FALSE
@@ -167,7 +166,6 @@
burst_size = 3
fire_delay = 1
spread = 5
- actions_types = list(/datum/action/item_action/toggle_firemode)
realistic = TRUE
dirt_modifier = 0.1
can_flashlight = TRUE
@@ -391,7 +389,6 @@
can_suppress = FALSE
burst_size = 3
spread = 10
- actions_types = list(/datum/action/item_action/toggle_firemode)
can_bayonet = FALSE
mag_display = TRUE
mag_display_ammo = TRUE
@@ -440,7 +437,6 @@
burst_size = 3
fire_delay = 3
spread = 15
- actions_types = list(/datum/action/item_action/toggle_firemode)
mag_display = TRUE
mag_display_ammo = TRUE
realistic = TRUE
@@ -489,7 +485,6 @@
fire_delay = 0.7
can_suppress = FALSE
burst_size = 4
- actions_types = list(/datum/action/item_action/toggle_firemode)
mag_display = TRUE
mag_display_ammo = TRUE
realistic = TRUE
@@ -538,7 +533,7 @@
spread = 20
mag_display = FALSE
mag_display_ammo = FALSE
- actions_types = list(/datum/action/item_action/toggle_firemode)
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT)
realistic = TRUE
dirt_modifier = 1.7
emp_damageable = TRUE
@@ -578,7 +573,7 @@
mag_display_ammo = FALSE
burst_size = 1
fire_delay = 0
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
fire_sound = 'sound/weapons/gun/rifle/shot.ogg'
rack_sound = 'sound/weapons/gun/smg/smgrack.ogg'
lock_back_sound = 'sound/weapons/gun/pistol/slide_lock.ogg'
@@ -641,6 +636,7 @@
fire_sound = 'modular_skyrat/modules/sec_haul/sound/ltrifle_fire.ogg'
emp_damageable = TRUE
armadyne = TRUE
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT)
/obj/item/ammo_box/magazine/multi_sprite/norwind
name = "norwind dmr magazine (12mm)"
@@ -675,7 +671,6 @@
inhand_icon_state = "vintorez"
mag_type = /obj/item/ammo_box/magazine/multi_sprite/vintorez
can_suppress = FALSE
- actions_types = list(/datum/action/item_action/toggle_firemode)
can_bayonet = FALSE
mag_display = FALSE
mag_display_ammo = FALSE
@@ -729,7 +724,6 @@
fire_delay = 1.7
can_suppress = FALSE
burst_size = 3
- actions_types = list(/datum/action/item_action/toggle_firemode)
can_bayonet = FALSE
mag_display = TRUE
mag_display_ammo = TRUE
diff --git a/tgstation.dme b/tgstation.dme
index 85a675e9dc9..6f14bde0835 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -155,6 +155,7 @@
#include "code\__DEFINES\~skyrat_defines\economy.dm"
#include "code\__DEFINES\~skyrat_defines\examine_defines.dm"
#include "code\__DEFINES\~skyrat_defines\faction.dm"
+#include "code\__DEFINES\~skyrat_defines\gun.dm"
#include "code\__DEFINES\~skyrat_defines\integrated_electronics.dm"
#include "code\__DEFINES\~skyrat_defines\inventory.dm"
#include "code\__DEFINES\~skyrat_defines\jobs.dm"
@@ -3492,6 +3493,7 @@
#include "modular_skyrat\master_files\code\controllers\processing\circuit.dm"
#include "modular_skyrat\master_files\code\controllers\subsystem\statpanel.dm"
#include "modular_skyrat\master_files\code\datums\ert.dm"
+#include "modular_skyrat\master_files\code\datums\components\fullauto.dm"
#include "modular_skyrat\master_files\code\datums\id_trim\jobs.dm"
#include "modular_skyrat\master_files\code\datums\id_trim\syndicate.dm"
#include "modular_skyrat\master_files\code\datums\martial\cqcplus.dm"
@@ -3518,6 +3520,8 @@
#include "modular_skyrat\master_files\code\modules\mob\living\carbon\carbon_say.dm"
#include "modular_skyrat\master_files\code\modules\mob\living\simple_animal\friendly\bumbles.dm"
#include "modular_skyrat\master_files\code\modules\power\lighting.dm"
+#include "modular_skyrat\master_files\code\modules\projectiles\guns\gun.dm"
+#include "modular_skyrat\master_files\code\modules\projectiles\guns\ballistic\automatic.dm"
#include "modular_skyrat\master_files\code\modules\uplink\uplink_items.dm"
#include "modular_skyrat\modules\3516\code\3516.dm"
#include "modular_skyrat\modules\3516\code\bodypart_autosurgeon.dm"