diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 47b3648054..7a54b8482d 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -321,6 +321,12 @@
///from base of mob/AltClickOn(): (atom/A)
#define COMSIG_MOB_ALTCLICKON "mob_altclickon"
+//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"
+
// Lighting:
///from base of [atom/proc/set_light]: (l_range, l_power, l_color, l_on)
#define COMSIG_ATOM_SET_LIGHT "atom_set_light"
diff --git a/code/__DEFINES/gun.dm b/code/__DEFINES/gun.dm
new file mode 100644
index 0000000000..05ce5118a3
--- /dev/null
+++ b/code/__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/datums/action.dm b/code/datums/action.dm
index ac8c909bd2..304aa47baa 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -23,6 +23,8 @@
var/icon_icon = 'icons/mob/actions.dmi' //This is the file for the ACTION icon
var/button_icon_state = "default" //And this is the state for the action icon
var/mob/owner
+ ///List of all mobs that are viewing our action button -> A unique movable for them to view.
+ var/list/viewers = list()
/datum/action/New(Target)
link_to(Target)
@@ -121,6 +123,11 @@
return FALSE
return TRUE
+/datum/action/proc/UpdateButtons(status_only, force)
+ for(var/datum/hud/hud in viewers)
+ var/atom/movable/screen/movable/button = viewers[hud]
+ UpdateButtonIcon(button, status_only, force)
+
/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
if(!button)
return
@@ -217,6 +224,8 @@
name = "Toggle Hood"
/datum/action/item_action/toggle_firemode
+ icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "fireselect_no"
name = "Toggle Firemode"
/datum/action/item_action/rcl_col
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 41356583e6..ff8c52489b 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -89,6 +89,19 @@
var/zoom_out_amt = 0
var/datum/action/item_action/toggle_scope_zoom/azoom
+ 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/toggle_safety_action
+
+ 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
+
var/dualwield_spread_mult = 1 //dualwield spread multiplier
/// Just 'slightly' snowflakey way to modify projectile damage for projectiles fired from this gun.
@@ -97,18 +110,53 @@
/// directional recoil multiplier
var/dir_recoil_amp = 10
+/datum/action/item_action/toggle_safety
+ name = "Toggle Safety"
+ icon_icon = 'icons/hud/actions.dmi'
+ button_icon_state = "safety_on"
-/obj/item/gun/Initialize(mapload)
+/obj/item/gun/ui_action_click(mob/user, actiontype)
+ if(istype(actiontype, /datum/action/item_action/toggle_firemode))
+ fire_select()
+ else if(istype(actiontype, toggle_safety_action))
+ toggle_safety(user)
+ else
+ ..()
+
+
+/obj/item/gun/Initialize()
. = ..()
- if(no_pin_required)
- pin = null
- else if(pin)
+ if(pin)
pin = new pin(src)
+
if(gun_light)
- alight = new (src)
+ alight = new(src)
+
if(zoomable)
azoom = new (src)
+ if(has_gun_safety)
+ safety = TRUE
+ toggle_safety_action = 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(pin)
QDEL_NULL(pin)
@@ -118,6 +166,10 @@
QDEL_NULL(bayonet)
if(chambered)
QDEL_NULL(chambered)
+ if(toggle_safety_action)
+ QDEL_NULL(toggle_safety_action)
+ if(firemode_action)
+ QDEL_NULL(firemode_action)
return ..()
/obj/item/gun/examine(mob/user)
@@ -143,6 +195,61 @@
else if(can_bayonet)
. += "It has a bayonet lug on it."
+/obj/item/gun/proc/fire_select()
+ var/mob/living/carbon/human/user = usr
+
+ var/max_mode = fire_select_modes.len
+
+ if(max_mode <= 1)
+ balloon_alert(user, "only one firemode!")
+ 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)
+ balloon_alert(user, "semi-automatic")
+ if(SELECT_BURST_SHOT)
+ burst_size = initial(burst_size)
+ fire_delay = initial(fire_delay)
+ SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_DESELECTED, user)
+ balloon_alert(user, "[burst_size]-round burst")
+ if(SELECT_FULLY_AUTOMATIC)
+ burst_size = 1
+ SEND_SIGNAL(src, COMSIG_GUN_AUTOFIRE_SELECTED, user)
+ balloon_alert(user, "automatic")
+
+ playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
+ update_appearance()
+ firemode_action.button_icon_state = "fireselect_[fire_select]"
+ firemode_action.UpdateButtons()
+ //SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) I'll need this later
+ return TRUE
+
+/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
+ toggle_safety_action.button_icon_state = "safety_[safety ? "on" : "off"]"
+ toggle_safety_action.UpdateButtons()
+ playsound(src, 'sound/weapons/empty.ogg', 100, TRUE)
+ user.visible_message(
+ span_notice("[user] toggles [src]'s safety [safety ? "ON" : "OFF"]."),
+ span_notice("You toggle [src]'s safety [safety ? "ON" : "OFF"].")
+ )
+ //SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) once again, needed later
+
/obj/item/gun/equipped(mob/living/user, slot)
. = ..()
if(zoomed && user.get_active_held_item() != src)
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index dd68ed9817..f43999d62b 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -6,7 +6,7 @@
can_suppress = TRUE
burst_size = 3
burst_shot_delay = 2
- actions_types = list(/datum/action/item_action/toggle_firemode)
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT, SELECT_FULLY_AUTOMATIC)
/obj/item/gun/ballistic/automatic/proto
name = "\improper Nanotrasen Saber SMG"
diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm
index c53366c4f5..10a6eea89d 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)
casing_ejector = FALSE
/obj/item/gun/ballistic/automatic/gyropistol/update_icon_state()
diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
index 98b654aadb..bd6f203882 100644
--- a/code/modules/projectiles/guns/ballistic/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -7,7 +7,7 @@
can_suppress = TRUE
burst_size = 1
fire_delay = 0
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
automatic_burst_overlay = FALSE
/obj/item/gun/ballistic/automatic/pistol/no_mag
@@ -104,7 +104,7 @@
mag_type = /obj/item/ammo_box/magazine/pistolm9mm
burst_size = 3
fire_delay = 2
- actions_types = list(/datum/action/item_action/toggle_firemode)
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC, SELECT_BURST_SHOT, SELECT_FULLY_AUTOMATIC)
/obj/item/gun/ballistic/automatic/pistol/stickman
name = "flat gun"
@@ -137,7 +137,7 @@
burst_size = 1
can_suppress = FALSE
w_class = WEIGHT_CLASS_NORMAL
- actions_types = list()
+ fire_select_modes = list(SELECT_SEMI_AUTOMATIC)
fire_sound = 'sound/weapons/noscope.ogg'
spread = 20 //damn thing has no rifling.
automatic_burst_overlay = FALSE
diff --git a/icons/hud/actions.dmi b/icons/hud/actions.dmi
new file mode 100644
index 0000000000..5d718bc9e0
Binary files /dev/null and b/icons/hud/actions.dmi differ
diff --git a/icons/mob/actions/actions_items.dmi b/icons/mob/actions/actions_items.dmi
index 10627cf66d..2eb92f4a45 100644
Binary files a/icons/mob/actions/actions_items.dmi and b/icons/mob/actions/actions_items.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index f1e5e5bf27..48c2a7d9de 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -56,6 +56,7 @@
#include "code\__DEFINES\fantasy_affixes.dm"
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
+#include "code\__DEFINES\gun.dm"
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\instruments.dm"
#include "code\__DEFINES\integrated_electronics.dm"