diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 99add07ecc06..65d55014d2f2 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -112,8 +112,23 @@
#define MOVESET_ROLES "moveset_role"
#define MOVESET_QUALITY "moveset_quality"
+
+//Autofire component
+/// Compatible firemode is in the gun. Wait until it's held in the user hands.
+#define AUTOFIRE_STAT_IDLE (1<<0)
+/// Gun is active and in the user hands. Wait until user does a valid click.
+#define AUTOFIRE_STAT_ALERT (1<<1)
+/// Gun is shooting.
+#define AUTOFIRE_STAT_FIRING (1<<2)
+
+#define COMSIG_AUTOFIRE_ONMOUSEDOWN "autofire_onmousedown"
+ #define COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS (1<<0)
+#define COMSIG_AUTOFIRE_SHOT "autofire_shot"
+ #define COMPONENT_AUTOFIRE_SHOT_SUCCESS (1<<0)
+
//Painkiller effectiveness (for get_painkiller_effect() comparison)
#define PAINKILLERS_EFFECT_SLIGHT 0.95 //all painkillers.
#define PAINKILLERS_EFFECT_MEDIUM 0.75 //weak painkillers, allow you to ignore minor pain and not see pain() messages.
#define PAINKILLERS_EFFECT_HEAVY 0.6 //powerful painkillers, allow you to not see custom_pain() messages.
#define PAINKILLERS_EFFECT_VERY_HEAVY 0.5 //very powerful painkillers that does not allow the user to determine the location of the injury.
+
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index e6be0a84a9cf..7aeae642e3e5 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -424,3 +424,11 @@
#define COMSIG_REMOVE_GENE_DISABILITY "remove_gene_disability"
// send this signal to handle disabilities in life for mob/living/carbon/human
#define COMSIG_HANDLE_DISABILITIES "handle_disabilities"
+
+//from base of client/MouseDown(): (/client, object, location, control, params)
+#define COMSIG_CLIENT_MOUSEDOWN "client_mousedown"
+//from base of client/MouseUp(): (/client, object, location, control, params)
+#define COMSIG_CLIENT_MOUSEUP "client_mouseup"
+ #define COMPONENT_CLIENT_MOUSEUP_INTERCEPT (1<<0)
+//from base of client/MouseUp(): (/client, object, location, control, params)
+#define COMSIG_CLIENT_MOUSEDRAG "client_mousedrag"
diff --git a/code/__DEFINES/subsystem.dm b/code/__DEFINES/subsystem.dm
index f9b91ad1fdd6..30133b79d53c 100644
--- a/code/__DEFINES/subsystem.dm
+++ b/code/__DEFINES/subsystem.dm
@@ -81,6 +81,7 @@
#define SS_PRIORITY_LOW 1
+#define SS_WAIT_FULLAUTO 1
#define SS_WAIT_EXPLOSION 1
#define SS_WAIT_INPUT 1
#define SS_WAIT_DEMO 1
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index f5254f82ecef..dbbc836fccaf 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -197,6 +197,7 @@
#define TRAIT_MIMING "miming"
#define TRAIT_WILLPOWER_IMPLANT "willpower_implant"
#define TRAIT_CAN_LEAP "can_leap"
+#define TRAIT_AUTOFIRE_SHOOTS "autofire_shoots"
/*
* Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas.
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 0c3785628578..41209e09c563 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1662,3 +1662,23 @@ var/global/list/WALLITEMS = typecacheof(list(
location = location.loc
if(location && include_turf) //At this point, only the turf is left, provided it exists.
. += location
+
+/proc/parse_caught_click_modifiers(list/modifiers, turf/origin, client/viewing_client)
+ if(!modifiers)
+ return null
+
+ var/screen_loc = splittext(LAZYACCESS(modifiers, SCREEN_LOC), ",")
+ var/list/actual_view = getviewsize(viewing_client ? viewing_client.view : world.view)
+ var/click_turf_x = splittext(screen_loc[1], ":")
+ var/click_turf_y = splittext(screen_loc[2], ":")
+ var/click_turf_z = origin.z
+
+ var/click_turf_px = text2num(click_turf_x[2])
+ var/click_turf_py = text2num(click_turf_y[2])
+ click_turf_x = origin.x + text2num(click_turf_x[1]) - round(actual_view[1] / 2) - 1
+ click_turf_y = origin.y + text2num(click_turf_y[1]) - round(actual_view[2] / 2) - 1
+
+ var/turf/click_turf = locate(clamp(click_turf_x, 1, world.maxx), clamp(click_turf_y, 1, world.maxy), click_turf_z)
+ LAZYSET(modifiers, ICON_X, "[(click_turf_px - click_turf.pixel_x) + ((click_turf_x - click_turf.x) * world.icon_size)]")
+ LAZYSET(modifiers, ICON_Y, "[(click_turf_py - click_turf.pixel_y) + ((click_turf_y - click_turf.y) * world.icon_size)]")
+ return click_turf
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 79e3368daacf..7d0761e32eaa 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -53,6 +53,11 @@
* mob/RangedAttack(atom,params) - used only ranged, only used for tk and laser eyes but could be changed
*/
/mob/proc/ClickOn( atom/A, params )
+ if(client.click_intercept_time)
+ if(client.click_intercept_time >= world.time)
+ client.click_intercept_time = 0 //Reset and return. Next click should work, but not this one.
+ return
+ client.click_intercept_time = 0 //Just reset. Let's not keep re-checking forever.
if(world.time <= next_click)
return
next_click = world.time + 1
@@ -160,6 +165,18 @@
else
RangedAttack(A, params)
+/client/MouseDown(datum/object, location, control, params)
+ SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDOWN, object, location, control, params)
+ ..()
+
+/client/MouseUp(object, location, control, params)
+ if(SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEUP, object, location, control, params) & COMPONENT_CLIENT_MOUSEUP_INTERCEPT)
+ click_intercept_time = world.time
+
+/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
+ SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDRAG, src_object, over_object, src_location, over_location, src_control, over_control, params)
+ ..()
+
// Default behavior: ignore double clicks (don't add normal clicks, as it will do three clicks instead of two with double).
/mob/proc/DblClickOn(atom/A, params)
return
diff --git a/code/controllers/subsystem/fullauto.dm b/code/controllers/subsystem/fullauto.dm
new file mode 100644
index 000000000000..856bc49f8e9d
--- /dev/null
+++ b/code/controllers/subsystem/fullauto.dm
@@ -0,0 +1,5 @@
+PROCESSING_SUBSYSTEM_DEF(fullauto)
+ name = "Fullauto"
+ flags = SS_NO_INIT | SS_BACKGROUND
+ priority = SS_PRIORITY_LOW
+ wait = SS_WAIT_FULLAUTO
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index 2fe896c8820e..52928242093f 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -151,6 +151,10 @@
if(!length(signal_procs[target]))
signal_procs -= target
+/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
+ for (var/signal_type in signal_types)
+ RegisterSignal(target, signal_type, proctype, override)
+
/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
return
diff --git a/code/datums/components/fullauto.dm b/code/datums/components/fullauto.dm
new file mode 100644
index 000000000000..3bd03c5351e2
--- /dev/null
+++ b/code/datums/components/fullauto.dm
@@ -0,0 +1,290 @@
+#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
+ /// Time between individual shots.
+ var/autofire_shot_delay = 0.3 SECONDS
+ /// 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.
+ var/mouse_status = AUTOFIRE_MOUSEUP
+
+ ///windup autofire vars
+ ///Whether the delay between shots increases over time, simulating a spooling weapon
+ var/windup_autofire = FALSE
+ ///the reduction to shot delay for windup
+ var/current_windup_reduction = 0
+ ///the percentage of autfire_shot_delay that is added to current_windup_reduction
+ var/windup_autofire_reduction_multiplier = 0.3
+ ///How high of a reduction that current_windup_reduction can reach
+ var/windup_autofire_cap = 0.3
+ ///How long it takes for weapons that have spooled-up to reset back to the original firing speed
+ var/windup_spindown = 3 SECONDS
+ ///Timer for tracking the spindown reset timings
+ var/timerid
+ COOLDOWN_DECLARE(next_shot_cd)
+
+/datum/component/automatic_fire/Initialize(autofire_shot_delay, windup_autofire, windup_autofire_reduction_multiplier, windup_autofire_cap, windup_spindown)
+ . = ..()
+ if(!istype(parent, /obj/item/weapon/gun))
+ return COMPONENT_NOT_ATTACHED
+ var/obj/item/weapon/gun/gun = parent
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(wake_up))
+ if(autofire_shot_delay)
+ src.autofire_shot_delay = autofire_shot_delay
+ if(windup_autofire)
+ src.windup_autofire = windup_autofire
+ src.windup_autofire_reduction_multiplier = windup_autofire_reduction_multiplier
+ src.windup_autofire_cap = windup_autofire_cap
+ src.windup_spindown = windup_spindown
+ if(autofire_stat == AUTOFIRE_STAT_IDLE && ismob(gun.loc))
+ var/mob/user = gun.loc
+ wake_up(src, user)
+
+
+/datum/component/automatic_fire/Destroy()
+ autofire_off()
+ return ..()
+
+/datum/component/automatic_fire/process(seconds_per_tick)
+ if(autofire_stat != AUTOFIRE_STAT_FIRING)
+ STOP_PROCESSING(SSfullauto, src)
+ 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
+ if(user.get_active_hand() == parent)
+ autofire_on(user.client)
+
+// 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_IDLE)
+ return
+ autofire_stat = AUTOFIRE_STAT_ALERT
+ if(!QDELETED(usercli))
+ clicker = usercli
+ shooter = clicker.mob
+ RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(on_mouse_down))
+ if(!QDELETED(shooter))
+ RegisterSignal(shooter, COMSIG_LOGOUT, PROC_REF(autofire_off))
+ UnregisterSignal(shooter, COMSIG_LOGIN)
+ RegisterSignals(parent, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED), PROC_REF(autofire_off))
+ parent.RegisterSignal(src, COMSIG_AUTOFIRE_ONMOUSEDOWN, TYPE_PROC_REF(/obj/item/weapon/gun, autofire_bypass_check))
+ parent.RegisterSignal(parent, COMSIG_AUTOFIRE_SHOT, TYPE_PROC_REF(/obj/item/weapon/gun, 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
+ if(!QDELETED(shooter))
+ RegisterSignal(shooter, COMSIG_LOGIN, PROC_REF(on_client_login))
+ UnregisterSignal(shooter, COMSIG_LOGOUT)
+ UnregisterSignal(parent, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED))
+ 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.get_active_hand() == src)
+ autofire_on(source.client)
+
+/datum/component/automatic_fire/proc/on_mouse_down(client/source, atom/_target, turf/location, control, params)
+ SIGNAL_HANDLER
+ var/list/modifiers = params2list(params) //If they're shift+clicking, for example, let's not have them accidentally shoot.
+
+ if(modifiers[SHIFT_CLICK] || modifiers[MIDDLE_CLICK] || modifiers[RIGHT_CLICK] || modifiers[ALT_CLICK])
+ return
+ if(source.mob.in_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) || istype(_target, /atom/movable/screen)) //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 = parse_caught_click_modifiers(modifiers, get_turf(source.eye), source)
+ params = list2params(modifiers)
+ 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
+ INVOKE_ASYNC(src, PROC_REF(start_autofiring))
+
+/datum/component/automatic_fire/proc/start_autofiring()
+ if(autofire_stat == AUTOFIRE_STAT_FIRING)
+ return
+ autofire_stat = AUTOFIRE_STAT_FIRING
+
+ clicker.mouse_override_icon = 'icons/hud/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_REF(on_mouse_up))
+ mouse_status = AUTOFIRE_MOUSEDOWN
+
+ RegisterSignal(shooter, COMSIG_MOB_SWAP_HANDS, PROC_REF(stop_autofiring))
+
+ if(!istype(parent, /obj/item/weapon/gun))
+ var/obj/item/weapon/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.
+
+ ADD_TRAIT(shooter, TRAIT_AUTOFIRE_SHOOTS, GENERIC_TRAIT)
+ START_PROCESSING(SSfullauto, src)
+ RegisterSignal(clicker, COMSIG_CLIENT_MOUSEDRAG, PROC_REF(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
+ if(autofire_stat != AUTOFIRE_STAT_FIRING)
+ return
+ STOP_PROCESSING(SSfullauto, 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)
+ REMOVE_TRAIT(shooter, TRAIT_AUTOFIRE_SHOOTS, GENERIC_TRAIT)
+ 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(!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 = parse_caught_click_modifiers(modifiers, get_turf(source.eye), source)
+ params = list2params(modifiers)
+ 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 FALSE
+ if(!COOLDOWN_FINISHED(src, next_shot_cd))
+ return TRUE
+ 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
+ shooter.face_atom(target)
+ var/next_delay = autofire_shot_delay
+ if(windup_autofire)
+ next_delay = clamp(next_delay - current_windup_reduction, round(autofire_shot_delay * windup_autofire_cap), autofire_shot_delay)
+ current_windup_reduction = (current_windup_reduction + round(autofire_shot_delay * windup_autofire_reduction_multiplier))
+ timerid = addtimer(CALLBACK(src, PROC_REF(windup_reset), FALSE), windup_spindown, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE)
+ COOLDOWN_START(src, next_shot_cd, next_delay)
+ if(SEND_SIGNAL(parent, COMSIG_AUTOFIRE_SHOT, target, shooter, mouse_parameters) & COMPONENT_AUTOFIRE_SHOT_SUCCESS)
+ return TRUE
+ stop_autofiring()
+ return FALSE
+
+/// Reset for our windup, resetting everything back to initial values after a variable set amount of time (determined by var/windup_spindown).
+/datum/component/automatic_fire/proc/windup_reset(deltimer)
+ current_windup_reduction = initial(current_windup_reduction)
+ if(deltimer && timerid)
+ deltimer(timerid)
+
+// Gun procs.
+
+/obj/item/weapon/gun/proc/on_autofire_start(mob/living/shooter)
+ if(!ready_to_fire() || shooter.incapacitated())
+ return FALSE
+ if(!can_fire())
+ shoot_with_empty_chamber(shooter)
+ return FALSE
+ return TRUE
+
+/obj/item/weapon/gun/proc/autofire_bypass_check(datum/source, client/clicker, atom/target, turf/location, control, params)
+ SIGNAL_HANDLER
+ if(clicker.mob.get_active_hand() != src)
+ return COMPONENT_AUTOFIRE_ONMOUSEDOWN_BYPASS
+
+
+/obj/item/weapon/gun/proc/do_autofire(datum/source, atom/target, mob/living/shooter, params)
+ SIGNAL_HANDLER
+ if(!ready_to_fire() || shooter.incapacitated())
+ return NONE
+ if(!can_fire())
+ shoot_with_empty_chamber(shooter)
+ return NONE
+ INVOKE_ASYNC(src, PROC_REF(do_autofire_shot), source, target, shooter, params)
+ return COMPONENT_AUTOFIRE_SHOT_SUCCESS //All is well, we can continue shooting.
+
+/obj/item/weapon/gun/proc/do_autofire_shot(datum/source, atom/target, mob/living/shooter, params)
+ afterattack(target, shooter, FALSE)
+
+#undef AUTOFIRE_MOUSEUP
+#undef AUTOFIRE_MOUSEDOWN
diff --git a/code/datums/uplinks_items.dm b/code/datums/uplinks_items.dm
index a1fe5f67feb1..63c8e898c5d6 100644
--- a/code/datums/uplinks_items.dm
+++ b/code/datums/uplinks_items.dm
@@ -242,7 +242,7 @@
/datum/uplink_item/dangerous/machinegun
name = "L6 Squad Automatic Weapon"
- desc = "A traditionally constructed machine gun made by AA-2531. This deadly weapon has a massive 50-round magazine of 7.62x51mm ammunition."
+ desc = "A traditionally constructed machine gun made by AA-2531. This deadly weapon has a massive 100-round magazine of 7.62x51mm ammunition."
item = /obj/item/weapon/gun/projectile/automatic/l6_saw
cost = 30
uplink_types = list("nuclear")
@@ -522,7 +522,7 @@
/datum/uplink_item/ammo/machinegun
name = "Ammo-7.62x51mm"
- desc = "A 50-round magazine of 7.62x51mm ammunition for use in the L6 SAW machinegun. By the time you need to use this, you'll already be on a pile of corpses."
+ desc = "A 100-round magazine of 7.62x51mm ammunition for use in the L6 SAW machinegun. By the time you need to use this, you'll already be on a pile of corpses."
item = /obj/item/ammo_box/magazine/saw
cost = 10
uplink_types = list("nuclear")
diff --git a/code/game/gamemodes/modes_gameplays/families/outfit.dm b/code/game/gamemodes/modes_gameplays/families/outfit.dm
index 0ade42619f2a..e3b2ed643949 100644
--- a/code/game/gamemodes/modes_gameplays/families/outfit.dm
+++ b/code/game/gamemodes/modes_gameplays/families/outfit.dm
@@ -65,7 +65,7 @@
head = /obj/item/clothing/head/helmet/laserproof/police
shoes = /obj/item/clothing/shoes/boots/combat
gloves = /obj/item/clothing/gloves/combat/police
- suit_store = /obj/item/weapon/gun/projectile/automatic
+ suit_store = /obj/item/weapon/gun/projectile/automatic/saber
backpack_contents = list(
/obj/item/weapon/storage/box/handcuffs = 1,
/obj/item/ammo_box/magazine/smg = 3,
diff --git a/code/game/objects/random/random_guns.dm b/code/game/objects/random/random_guns.dm
index 7742f2589511..af5342398ecb 100644
--- a/code/game/objects/random/random_guns.dm
+++ b/code/game/objects/random/random_guns.dm
@@ -92,7 +92,7 @@
return pick(\
prob(15);/obj/item/weapon/gun/projectile/shotgun/bolt_action,\
prob(15);/obj/item/weapon/gun/projectile/shotgun/repeater,\
- prob(15);/obj/item/weapon/gun/projectile/automatic,\
+ prob(15);/obj/item/weapon/gun/projectile/automatic/saber,\
prob(14);/obj/item/weapon/gun/projectile/automatic/c20r,\
prob(12);/obj/item/weapon/gun/projectile/automatic/mini_uzi,\
prob(10);/obj/item/weapon/gun/projectile/automatic/bar,\
@@ -188,7 +188,7 @@
/obj/item/weapon/gun/projectile/revolver/doublebarrel/dungeon/sawn_off,\
/obj/item/weapon/gun/projectile/revolver,\
/obj/item/weapon/gun/projectile/revolver/detective,\
- /obj/item/weapon/gun/projectile/automatic,\
+ /obj/item/weapon/gun/projectile/automatic/saber,\
/obj/item/weapon/gun/projectile/automatic/mini_uzi,\
/obj/item/weapon/gun/projectile/automatic/c20r,\
/obj/item/weapon/gun/projectile/automatic/l13,\
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index a737576889b8..ef38f33c5e50 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -174,6 +174,46 @@ var/global/list/all_supply_groups = list("Operations","Security","Hospitality","
access = access_brig
group = "Security"
+/datum/supply_pack/ballistic/smg
+ name = ".38 SMG crate"
+ contains = list(/obj/item/weapon/gun/projectile/automatic/l13,
+ /obj/item/weapon/gun/projectile/automatic/l13,
+ /obj/item/weapon/gun/projectile/automatic/l13)
+ additional_costs = 2300
+ crate_type = /obj/structure/closet/crate/secure/weapon
+ crate_name = ".38 SMG crate"
+ access = access_brig
+ group = "Security"
+
+/datum/supply_pack/ballistic/smg_magazine
+ name = ".38 magazine"
+ contains = list(/obj/item/ammo_box/magazine/l13/lethal,
+ /obj/item/ammo_box/magazine/l13/lethal,
+ /obj/item/ammo_box/magazine/l13/lethal,
+ /obj/item/ammo_box/magazine/l13/lethal,
+ /obj/item/ammo_box/magazine/l13/lethal,
+ /obj/item/ammo_box/magazine/l13/lethal)
+ additional_costs = 500
+ crate_type = /obj/structure/closet/crate/secure
+ crate_name = ".38 magazine"
+ access = access_armory
+ group = "Security"
+
+/datum/supply_pack/ballistic/smg_magazine_rubber
+ name = ".38 magazine (rubber)"
+ contains = list(/obj/item/ammo_box/magazine/l13,
+ /obj/item/ammo_box/magazine/l13,
+ /obj/item/ammo_box/magazine/l13,
+ /obj/item/ammo_box/magazine/l13,
+ /obj/item/ammo_box/magazine/l13,
+ /obj/item/ammo_box/magazine/l13)
+ additional_costs = 400
+ crate_type = /obj/structure/closet/crate/secure
+ crate_name = ".38 magazine (rubber)"
+ access = access_brig
+ group = "Security"
+
+
/datum/supply_pack/ballistic/pistol
name = "9mm pistol crate"
contains = list(/obj/item/weapon/gun/projectile/automatic/pistol/glock,
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 6b470e732a73..8e35c5318c0c 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -75,6 +75,8 @@
var/last_asset_job = 0
var/last_completed_asset_job = 0
+ ///Time when the click was intercepted
+ var/click_intercept_time = 0
///Amount of keydowns in the last keysend checking interval
var/client_keysend_amount = 0
@@ -118,3 +120,5 @@
COOLDOWN_DECLARE(say_slowmode)
var/is_in_spawner = FALSE
+ ///used to override the mouse cursor so it doesnt get reset
+ var/mouse_override_icon = null
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index ddd9358ab0c7..d1e52bba2bea 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -667,7 +667,7 @@ var/global/mining_shuttle_location = 0 // 0 = station 13, 1 = mining station
damtype = BURN
hitsound = list('sound/weapons/sear.ogg')
ammo_type = list(/obj/item/ammo_casing/energy/laser/cutter)
- fire_delay = 3
+ fire_delay = 0
w_class = SIZE_SMALL //it is smaller than the pickaxe
origin_tech = "materials=4;phorontech=3;engineering=3"
desc = "The latest self-rechargeable low-power cutter using bursts of hot plasma. You could use it to cut limbs off of xenos! Or, you know, mine stuff."
@@ -697,16 +697,15 @@ var/global/mining_shuttle_location = 0 // 0 = station 13, 1 = mining station
if((iswallturf(target)) && (prob(destruction_chance)))
target.ex_act(EXPLODE_HEAVY)
-
/obj/item/weapon/gun/energy/laser/cutter/atom_init()
. = ..()
power_supply.AddComponent(/datum/component/cell_selfrecharge, 50)
+ AddComponent(/datum/component/automatic_fire, 0.4 SECONDS)
/obj/item/weapon/gun/energy/laser/cutter/emag_act(mob/user)
if(emagged)
return FALSE
ammo_type += new /obj/item/ammo_casing/energy/laser/cutter/emagged(src)
- fire_delay = 5
origin_tech += ";syndicate=1"
emagged = TRUE
to_chat(user, "Ошибка: Обнаружен несовместимый модуль. Ошибкаошибкаошибка.")
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index a5d8a7bf3f34..a186e529b11d 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -136,6 +136,9 @@
if(get_species() == UNATHI && bodytemperature > species.body_temperature)
tally -= min((bodytemperature - species.body_temperature) / 10, 1) //will be on the border of heat_level_1
+ if(HAS_TRAIT(src, TRAIT_AUTOFIRE_SHOOTS)) // so that you can’t run at full speed and shoot everyone and everything
+ tally += 0.75
+
return (tally + config.human_delay)
/mob/living/carbon/human/Process_Spacemove(movement_dir = 0)
diff --git a/code/modules/projectiles/ammunition/magazines.dm b/code/modules/projectiles/ammunition/magazines.dm
index dc095251e28c..f62b0ef51e59 100644
--- a/code/modules/projectiles/ammunition/magazines.dm
+++ b/code/modules/projectiles/ammunition/magazines.dm
@@ -331,11 +331,11 @@
origin_tech = "combat=2"
ammo_type = /obj/item/ammo_casing/a762
caliber = "a762"
- max_ammo = 50
+ max_ammo = 100
/obj/item/ammo_box/magazine/saw/update_icon()
..()
- icon_state = "[initial(icon_state)]-[round(ammo_count(),10)]"
+ icon_state = "[initial(icon_state)]-[round(ammo_count(),20)]"
/obj/item/ammo_box/magazine/chameleon
name = "magazine (.45)"
diff --git a/code/modules/projectiles/firing.dm b/code/modules/projectiles/firing.dm
index c71ca8829f6d..85549bad2870 100644
--- a/code/modules/projectiles/firing.dm
+++ b/code/modules/projectiles/firing.dm
@@ -50,6 +50,7 @@
qdel(BB)
BB = null
return 1
+ BB.dispersion += weapon.spread
BB.loc = get_turf(src)
BB.starting = get_turf(src)
BB.current = curloc
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index ed31e7d13761..121f87fe482a 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -39,6 +39,11 @@
var/fire_delay = 6
var/last_fired = 0
var/two_hand_weapon = FALSE
+ var/burst = 1 //burst size
+ var/burst_delay = 1 //cooldown between burst shots
+ var/spread_increase = 0 // per shot
+ var/spread_max = 0
+ var/spread = 0
lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/guns_righthand.dmi'
@@ -46,6 +51,12 @@
/datum/action/item_action/hands_free/switch_gun
name = "Switch Gun"
+/obj/item/weapon/gun/process()
+ if(spread == 0)
+ STOP_PROCESSING(SSfastprocess, src)
+ else
+ spread = clamp(spread - 0.1, 0, spread_max)
+
/obj/item/weapon/gun/examine(mob/user)
..()
if(two_hand_weapon)
@@ -80,12 +91,21 @@
var/skill_recoil_duration = max(DEFAULT_DURATION_RECOIL, apply_skill_bonus(user, recoil, list(/datum/skill/firearms = SKILL_LEVEL_TRAINED), multiplier = -0.5))
if(two_hand_weapon != DESIRABLE_TWOHAND)
shake_camera(user, skill_recoil_duration, OPTIMAL_POWER_RECOIL)
+ if(spread_increase)
+ spread = clamp(spread + spread_increase, 0, spread_max)
+ START_PROCESSING(SSfastprocess, src)
if(two_hand_weapon == DESIRABLE_TWOHAND)
//No OPTIMAL_POWER_RECOIL only for increasing user's motivation to drop other hand
if(user.get_inactive_hand())
shake_camera(user, recoil + 2, recoil + 1)
+ if(spread_increase)
+ spread = clamp(spread + spread_increase + 1, 0, spread_max)
+ START_PROCESSING(SSfastprocess, src)
else
shake_camera(user, skill_recoil_duration, OPTIMAL_POWER_RECOIL)
+ if(spread_increase)
+ spread = clamp(spread + spread_increase, 0, spread_max)
+ START_PROCESSING(SSfastprocess, src)
if(silenced)
playsound(user, fire_sound, VOL_EFFECTS_MASTER, 30, FALSE, null, -4)
@@ -174,25 +194,28 @@
return
if (!ready_to_fire())
- if (world.time % 3) //to prevent spam
- to_chat(user, "[src] is not ready to fire again!")
return
- if(chambered)
- if(point_blank)
- if(!chambered.BB.fake)
- user.visible_message(" \The [user] fires \the [src] point blank at [target]!")
- chambered.BB.damage *= 1.3
- if(!chambered.fire(src, target, user, params, , silenced))
- shoot_with_empty_chamber(user)
- else
- shoot_live_shot(user)
- user.newtonian_move(get_dir(target, user))
- else
- shoot_with_empty_chamber(user)
- process_chamber()
- update_icon()
- update_inv_mob()
+ user.next_click = world.time + (burst - 1) * burst_delay
+ for(var/i in 1 to burst)
+ if(chambered)
+ if(point_blank)
+ if(!chambered.BB.fake)
+ user.visible_message(" \The [user] fires \the [src] point blank at [target]!")
+ chambered.BB.damage *= 1.3
+ if(!chambered.fire(src, target, user, params, , silenced))
+ shoot_with_empty_chamber(user)
+ break
+ else
+ shoot_live_shot(user)
+ user.newtonian_move(get_dir(target, user))
+ else
+ shoot_with_empty_chamber(user)
+ break
+ sleep(burst_delay)
+ process_chamber()
+ update_icon()
+ update_inv_mob()
/obj/item/weapon/gun/proc/can_fire()
return
diff --git a/code/modules/projectiles/guns/plasma/plasma.dm b/code/modules/projectiles/guns/plasma/plasma.dm
index 0a7761add0a0..a7e256a658e8 100644
--- a/code/modules/projectiles/guns/plasma/plasma.dm
+++ b/code/modules/projectiles/guns/plasma/plasma.dm
@@ -16,11 +16,12 @@
desc = "Стандартный плазменный карабин типа булл-пап обладающий высокой скорострельностью."
icon_state = "plasma10_car"
item_state = "plasma10_car"
- fire_delay = 2
+ fire_delay = 0
origin_tech = "combat=3;magnets=2"
fire_sound = 'sound/weapons/guns/plasma10_shot.ogg'
recoil = FALSE
can_be_holstered = FALSE
+ var/fullauto = TRUE
var/overcharge_fire_sound = 'sound/weapons/guns/plasma10_overcharge_shot.ogg'
@@ -40,6 +41,7 @@
icon_state = "plasma104_stg"
item_state = "plasma104_stg"
origin_tech = "combat=4;magnets=3"
+ fullauto = FALSE
overcharge_fire_sound = 'sound/weapons/guns/plasma10_overcharge_massive_shot.ogg'
@@ -55,6 +57,8 @@
/obj/item/weapon/gun/plasma/atom_init()
. = ..()
+ if(fullauto)
+ AddComponent(/datum/component/automatic_fire, 0.2 SECONDS)
magazine = new initial_mag(src)
for(var/i in ammo_type)
var/path = ammo_type[i]
@@ -85,7 +89,7 @@
fire_sound = initial(fire_sound)
else
shot = ammo_type[PLASMAGUN_OVERCHARGE_TYPE]
- fire_delay = 1
+ fire_delay = 0
fire_sound = overcharge_fire_sound
max_projectile_per_fire = 1
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm
index be0134592d26..48b983faa04e 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/projectile/automatic.dm
@@ -1,6 +1,6 @@
-/obj/item/weapon/gun/projectile/automatic //Hopefully someone will find a way to make these fire in bursts or something. --Superxpdude
- name = "submachine gun"
- desc = "Легкий, скорострельный пистолет-пулемёт. Использует патроны калибра 9мм."
+/obj/item/weapon/gun/projectile/automatic
+ name = "generic automatic gun"
+ desc = "О боже, вы не должны были видеть это!"
icon_state = "saber"
item_state = null
w_class = SIZE_SMALL
@@ -46,6 +46,17 @@
return install_silencer(I, user, params)
return ..()
+/obj/item/weapon/gun/projectile/automatic/saber
+ name = "submachine gun"
+ desc = "Легкий, скорострельный пистолет-пулемёт. Использует патроны калибра 9мм."
+ spread_increase = 0.5
+ spread_max = 1.5
+ fire_delay = 0
+
+/obj/item/weapon/gun/projectile/automatic/saber/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.2 SECONDS)
+
/obj/item/weapon/gun/projectile/automatic/mini_uzi
name = "Mac-10"
desc = "Легкий и скорострельный пистолет-пулемёт для тех случаев, когда нужно кого-то быстро убить. Использует патроны калибра 9мм."
@@ -56,6 +67,13 @@
origin_tech = "combat=5;materials=2;syndicate=8"
initial_mag = /obj/item/ammo_box/magazine/mac10
can_be_silenced = TRUE
+ fire_delay = 0
+ spread_increase = 0.25
+ spread_max = 2
+
+/obj/item/weapon/gun/projectile/automatic/mini_uzi/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.1 SECONDS)
/obj/item/weapon/gun/projectile/automatic/c20r
name = "C-20r SMG"
@@ -70,6 +88,13 @@
should_alarm_when_empty = TRUE
can_be_silenced = TRUE
has_ammo_counter = TRUE
+ fire_delay = 0
+ spread_increase = 0.25
+ spread_max = 1.5
+
+/obj/item/weapon/gun/projectile/automatic/c20r/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.2 SECONDS)
/obj/item/weapon/gun/projectile/automatic/l6_saw
name = "L6 SAW"
@@ -83,9 +108,16 @@
has_cover = TRUE
two_hand_weapon = ONLY_TWOHAND
has_ammo_counter = TRUE
+ fire_delay = 0
+ spread_increase = 0.5
+ spread_max = 2
+
+/obj/item/weapon/gun/projectile/automatic/l6_saw/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.25 SECONDS)
/obj/item/weapon/gun/projectile/automatic/l6_saw/update_icon()
- icon_state = "l6[cover_open ? "open" : "closed"][magazine ? CEIL(get_ammo(0) / 12.5) * 25 : "-empty"]"
+ icon_state = "l6[cover_open ? "open" : "closed"][magazine ? CEIL(get_ammo(0) / 25) * 25 : "-empty"]"
item_state = "l6[cover_open ? "open" : "closed"][magazine ? "mag" : "nomag"]"
/obj/item/weapon/gun/projectile/automatic/l6_saw/afterattack(atom/target, mob/user, proximity, params) //what I tried to do here is just add a check to see if the cover is open or not and add an icon_state change because I can't figure out how c-20rs do it with overlays
@@ -136,6 +168,13 @@
suitable_mags = list(/obj/item/ammo_box/magazine/l13, /obj/item/ammo_box/magazine/l13/lethal)
fire_sound = 'sound/weapons/guns/gunshot_l13.ogg'
can_be_silenced = TRUE
+ fire_delay = 0
+ spread_increase = 0.25
+ spread_max = 1.5
+
+/obj/item/weapon/gun/projectile/automatic/l13/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.2 SECONDS)
/obj/item/weapon/gun/projectile/automatic/tommygun
name = "tommy gun"
@@ -149,6 +188,13 @@
initial_mag = /obj/item/ammo_box/magazine/tommygun
fire_sound = 'sound/weapons/guns/gunshot_light.ogg'
can_be_silenced = TRUE
+ fire_delay = 0
+ spread_increase = 0.25
+ spread_max = 2
+
+/obj/item/weapon/gun/projectile/automatic/tommygun/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.15 SECONDS)
/obj/item/weapon/gun/projectile/automatic/bar
name = "Browning M1918"
@@ -160,6 +206,13 @@
origin_tech = "combat=5;materials=2"
initial_mag = /obj/item/ammo_box/magazine/bar
fire_sound = 'sound/weapons/guns/Gunshot2.ogg'
+ fire_delay = 0
+ spread_increase = 0.5
+ spread_max = 1
+
+/obj/item/weapon/gun/projectile/automatic/bar/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.4 SECONDS)
/obj/item/weapon/gun/projectile/automatic/borg
name = "Robot SMG"
@@ -167,6 +220,8 @@
initial_mag = /obj/item/ammo_box/magazine/borg45
fire_sound = 'sound/weapons/guns/gunshot_medium.ogg'
has_ammo_counter = TRUE
+ burst = 3
+ burst_delay = 2
/obj/item/weapon/gun/projectile/automatic/borg/update_icon()
return
@@ -205,6 +260,13 @@
initial_mag = /obj/item/ammo_box/magazine/a28
suitable_mags = list(/obj/item/ammo_box/magazine/a28, /obj/item/ammo_box/magazine/a28/nonlethal, /obj/item/ammo_box/magazine/a28/incendiary)
fire_sound = 'sound/weapons/guns/gunshot_medium.ogg'
+ fire_delay = 0
+ spread_increase = 0.5
+ spread_max = 1.5
+
+/obj/item/weapon/gun/projectile/automatic/a28/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.25 SECONDS)
/obj/item/weapon/gun/projectile/automatic/a74
name = "A74 assault rifle"
@@ -217,6 +279,13 @@
item_state = "a74"
origin_tech = "combat=5;materials=4;syndicate=6"
fire_sound = 'sound/weapons/guns/gunshot_ak74.ogg'
+ fire_delay = 0
+ spread_increase = 0.5
+ spread_max = 1.5
+
+/obj/item/weapon/gun/projectile/automatic/a74/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.25 SECONDS)
/obj/item/weapon/gun/projectile/automatic/a74/krinkov
name = "Krinkov"
@@ -299,7 +368,10 @@
initial_mag = /obj/item/ammo_box/magazine/m41a
w_class = SIZE_SMALL
two_hand_weapon = DESIRABLE_TWOHAND
- fire_delay = 1
+ fire_delay = 3
+ burst = 3
+ spread_increase = 0.5
+ spread_max = 1.5
/obj/item/weapon/gun/projectile/automatic/m41a/process_chamber()
return ..(1, 1, 1)
@@ -325,10 +397,13 @@
/obj/item/weapon/gun/projectile/automatic/m41a/launcher/proc/toggle_gl(mob/user)
using_gl = !using_gl
if(using_gl)
+ spread = 0
+ burst = 1
user.visible_message("[user] presses a button, activating their [launcher]!",\
"You activate your [launcher].",\
"You hear an ominous click.")
else
+ burst = 3
user.visible_message("[user] presses a button, deciding to stop the bombings.",\
"You deactivate your [launcher].",\
"You hear a click.")
diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/projectile/pistol.dm
index 1525ed64b7ce..da7b49a15de4 100644
--- a/code/modules/projectiles/guns/projectile/pistol.dm
+++ b/code/modules/projectiles/guns/projectile/pistol.dm
@@ -65,6 +65,13 @@
initial_mag = /obj/item/ammo_box/magazine/stechkin
suitable_mags = list(/obj/item/ammo_box/magazine/stechkin, /obj/item/ammo_box/magazine/stechkin/extended)
can_be_silenced = TRUE
+ fire_delay = 0
+ spread_increase = 0.5
+ spread_max = 1.5
+
+/obj/item/weapon/gun/projectile/automatic/pistol/stechkin/atom_init()
+ . = ..()
+ AddComponent(/datum/component/automatic_fire, 0.3 SECONDS)
/obj/item/weapon/gun/projectile/automatic/pistol/colt1911
desc = "Дешевая марсианская подделка Colt M1911. Использует менее смертоносные патроны 45-го калибра."
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index 19f1150e5ca6..ba946827310d 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -238,7 +238,7 @@
proj_act_sound = SOUNDIN_WEAKBULLETACT
/obj/item/projectile/bullet/a762
- damage = 50
+ damage = 30
embed = 0
/obj/item/projectile/bullet/incendiary
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index ede37b37c08d..d07c41f77cb3 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -2083,7 +2083,7 @@ other types of metals and chemistry for reagents).
id = "smg"
build_type = PROTOLATHE
materials = list(MAT_METAL = 8000, MAT_SILVER = 2000, MAT_DIAMOND = 1000)
- build_path = /obj/item/weapon/gun/projectile/automatic
+ build_path = /obj/item/weapon/gun/projectile/automatic/saber
category = list("Weapons")
/datum/design/msmg9mm
diff --git a/code/modules/research/prototipify.dm b/code/modules/research/prototipify.dm
index 1983ceecae52..d712c0f0ed61 100644
--- a/code/modules/research/prototipify.dm
+++ b/code/modules/research/prototipify.dm
@@ -92,12 +92,17 @@
power_supply.maxcharge /= 2
power_supply.charge = power_supply.maxcharge
+/obj/item/weapon/gun/energy/laser/cutter/set_prototype_qualities(rel_val=100, mark=0)
+ if(mark)
+ power_supply.maxcharge += (mark - 1) * 200
+ if(!prob(reliability))
+ power_supply.maxcharge /= 2
+ power_supply.charge = power_supply.maxcharge
+
/obj/item/weapon/gun/projectile/automatic/set_prototype_qualities(rel_val=100, mark=0)
if(mark)
recoil = max(recoil / mark, 0.5)
- fire_delay = max(fire_delay / mark, 2)
if(!prob(reliability))
- fire_delay *= 2
recoil += 1
/obj/item/weapon/gun/plasma/set_prototype_qualities(rel_val=100, mark=0)
diff --git a/icons/hud/weapon_pointer.dmi b/icons/hud/weapon_pointer.dmi
new file mode 100644
index 000000000000..b5070062c0bb
Binary files /dev/null and b/icons/hud/weapon_pointer.dmi differ
diff --git a/icons/obj/ammo/magazines.dmi b/icons/obj/ammo/magazines.dmi
index 7ee6f6754b0e..4582c3c2f36d 100644
Binary files a/icons/obj/ammo/magazines.dmi and b/icons/obj/ammo/magazines.dmi differ
diff --git a/maps/centcom/centcom.dmm b/maps/centcom/centcom.dmm
index c8f3f5f20100..2c6d0b6e89eb 100644
--- a/maps/centcom/centcom.dmm
+++ b/maps/centcom/centcom.dmm
@@ -14441,13 +14441,13 @@
/area/custom/wizard_station)
"aGr" = (
/obj/structure/rack,
-/obj/item/weapon/gun/projectile/automatic{
+/obj/item/weapon/gun/projectile/automatic/saber{
pixel_y = 6
},
-/obj/item/weapon/gun/projectile/automatic{
+/obj/item/weapon/gun/projectile/automatic/saber{
pixel_y = 3
},
-/obj/item/weapon/gun/projectile/automatic,
+/obj/item/weapon/gun/projectile/automatic/saber,
/obj/machinery/camera{
c_tag = "Spec. Ops. Armory North";
network = list("ERT")
diff --git a/maps/templates/space_structures/old_station.dmm b/maps/templates/space_structures/old_station.dmm
index 72dc11c50b6f..3abc5fe6f605 100644
--- a/maps/templates/space_structures/old_station.dmm
+++ b/maps/templates/space_structures/old_station.dmm
@@ -6967,7 +6967,7 @@
/area/space_structures/old_station/central)
"OZ" = (
/obj/structure/rack,
-/obj/item/weapon/gun/projectile/automatic,
+/obj/item/weapon/gun/projectile/automatic/saber,
/obj/effect/decal/turf_decal/alpha/yellow{
icon_state = "bot"
},
diff --git a/taucetistation.dme b/taucetistation.dme
index e3766ada9b0d..4cbf3b8f140b 100644
--- a/taucetistation.dme
+++ b/taucetistation.dme
@@ -240,6 +240,7 @@
#include "code\controllers\subsystem\environment.dm"
#include "code\controllers\subsystem\events.dm"
#include "code\controllers\subsystem\explosions.dm"
+#include "code\controllers\subsystem\fullauto.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\holiday.dm"
#include "code\controllers\subsystem\holomaps.dm"
@@ -342,6 +343,7 @@
#include "code\datums\components\fishing.dm"
#include "code\datums\components\footstep.dm"
#include "code\datums\components\forcefield.dm"
+#include "code\datums\components\fullauto.dm"
#include "code\datums\components\gnawing.dm"
#include "code\datums\components\logout_spawner.dm"
#include "code\datums\components\magic_item.dm"