diff --git a/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm b/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm
index 87ec5cdaab..63972a98aa 100644
--- a/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm
+++ b/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm
@@ -1,19 +1,3 @@
-/datum/crafting_recipe/pin_removal
- name = "Pin Removal"
- result = /obj/item/gun
- reqs = list(/obj/item/gun = 1)
- parts = list(/obj/item/gun = 1)
- tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- time = 50
- category = CAT_WEAPONRY
- subcategory = CAT_OTHER
-
-/datum/crafting_recipe/pin_removal/check_requirements(mob/user, list/collected_requirements)
- var/obj/item/gun/G = collected_requirements[/obj/item/gun][1]
- if (G.no_pin_required || !G.pin)
- return FALSE
- return TRUE
-
/datum/crafting_recipe/strobeshield
name = "Strobe Shield"
result = /obj/item/shield/riot/flash
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 492768a8e0..3beeef91d3 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -21,8 +21,6 @@
var/active_sound = null
var/toggle_cooldown = null
var/cooldown = 0
- var/obj/item/flashlight/F = null
- var/can_flashlight = 0
var/blocks_shove_knockdown = FALSE //Whether wearing the clothing item blocks the ability for shove to knock down.
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index de68883a7b..6b698f9619 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -16,17 +16,47 @@
dog_fashion = /datum/dog_fashion/head/helmet
+ var/can_flashlight = FALSE //if a flashlight can be mounted. if it has a flashlight and this is false, it is permanently attached.
+ var/obj/item/flashlight/seclite/attached_light
+ var/datum/action/item_action/toggle_helmet_flashlight/alight
+
+/obj/item/clothing/head/helmet/Initialize(mapload)
+ . = ..()
+ if(attached_light)
+ alight = new(src)
+
/obj/item/clothing/head/helmet/ComponentInitialize()
. = ..()
AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_HEAD))
+/obj/item/clothing/head/helmet/examine(mob/user)
+ . = ..()
+ if(attached_light)
+ . += "It has \a [attached_light] [can_flashlight ? "" : "permanently "]mounted on it."
+ if(can_flashlight)
+ . += "[attached_light] looks like it can be unscrewed from [src]."
+ else if(can_flashlight)
+ . += "It has a mounting point for a seclite."
+
+/obj/item/clothing/head/helmet/Destroy()
+ QDEL_NULL(attached_light)
+ return ..()
+
+/obj/item/clothing/head/helmet/handle_atom_del(atom/A)
+ if(A == attached_light)
+ attached_light = null
+ update_helmlight()
+ update_icon()
+ QDEL_NULL(alight)
+ return ..()
+
/obj/item/clothing/head/helmet/sec
can_flashlight = 1
/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params)
if(issignaler(I))
var/obj/item/assembly/signaler/S = I
- if(F) //Has a flashlight. Player must remove it, else it will be lost forever.
+ if(attached_light) //Has a flashlight. Player must remove it, else it will be lost forever.
to_chat(user, "The mounted flashlight is in the way, remove it first!")
return
@@ -280,8 +310,8 @@
/obj/item/clothing/head/helmet/update_icon_state()
var/state = "[initial(icon_state)]"
- if(F)
- if(F.on)
+ if(attached_light)
+ if(attached_light.on)
state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on"
else
state += "-flight" //etc.
@@ -289,7 +319,7 @@
icon_state = state
/obj/item/clothing/head/helmet/ui_action_click(mob/user, action)
- if(istype(action, /datum/action/item_action/toggle_helmet_flashlight))
+ if(istype(action, alight))
toggle_helmlight()
else
..()
@@ -297,60 +327,60 @@
/obj/item/clothing/head/helmet/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/flashlight/seclite))
var/obj/item/flashlight/seclite/S = I
- if(can_flashlight)
- if(!F)
- if(!user.transferItemToLoc(S, src))
- return
- to_chat(user, "You click [S] into place on [src].")
- if(S.on)
- set_light(0)
- F = S
- update_icon()
- update_helmlight(user)
- verbs += /obj/item/clothing/head/helmet/proc/toggle_helmlight
- var/datum/action/A = new /datum/action/item_action/toggle_helmet_flashlight(src)
- if(loc == user)
- A.Grant(user)
+ if(can_flashlight && !attached_light)
+ if(!user.transferItemToLoc(S, src))
+ return
+ to_chat(user, "You click [S] into place on [src].")
+ if(S.on)
+ set_light(0)
+ attached_light = S
+ update_icon()
+ update_helmlight()
+ alight = new(src)
+ if(loc == user)
+ alight.Grant(user)
return
-
- if(I.tool_behaviour == TOOL_SCREWDRIVER)
- if(F)
- for(var/obj/item/flashlight/seclite/S in src)
- to_chat(user, "You unscrew the seclite from [src].")
- F = null
- S.forceMove(user.drop_location())
- update_helmlight(user)
- S.update_brightness(user)
- update_icon()
- usr.update_inv_head()
- verbs -= /obj/item/clothing/head/helmet/proc/toggle_helmlight
- for(var/datum/action/item_action/toggle_helmet_flashlight/THL in actions)
- qdel(THL)
- return
-
return ..()
+/obj/item/clothing/head/helmet/screwdriver_act(mob/living/user, obj/item/I)
+ ..()
+ if(can_flashlight && attached_light) //if it has a light but can_flashlight is false, the light is permanently attached.
+ I.play_tool_sound(src)
+ to_chat(user, "You unscrew [attached_light] from [src].")
+ attached_light.forceMove(drop_location())
+ if(Adjacent(user) && !issilicon(user))
+ user.put_in_hands(attached_light)
+
+ var/obj/item/flashlight/removed_light = attached_light
+ attached_light = null
+ update_helmlight()
+ removed_light.update_brightness(user)
+ update_icon()
+ user.update_inv_head()
+ QDEL_NULL(alight)
+ return TRUE
+
/obj/item/clothing/head/helmet/proc/toggle_helmlight()
set name = "Toggle Helmetlight"
set category = "Object"
set desc = "Click to toggle your helmet's attached flashlight."
- if(!F)
+ if(!attached_light)
return
var/mob/user = usr
if(user.incapacitated())
return
- F.on = !F.on
- to_chat(user, "You toggle the helmetlight [F.on ? "on":"off"].")
+ attached_light.on = !attached_light.on
+ to_chat(user, "You toggle the helmet-light [attached_light.on ? "on":"off"].")
playsound(user, 'sound/weapons/empty.ogg', 100, 1)
update_helmlight(user)
/obj/item/clothing/head/helmet/proc/update_helmlight(mob/user = null)
- if(F)
- if(F.on)
- set_light(F.brightness_on, F.flashlight_power, F.light_color)
+ if(attached_light)
+ if(attached_light.on)
+ set_light(attached_light.brightness_on, attached_light.flashlight_power, attached_light.light_color)
else
set_light(0)
update_icon()
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 06bf298454..bbd77ad665 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -1,5 +1,6 @@
#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4
+#define FIRING_PIN_REMOVAL_DELAY 50
/obj/item/gun
name = "gun"
@@ -63,7 +64,7 @@
var/obj/item/firing_pin/pin = /obj/item/firing_pin //standard firing pin for most guns
var/no_pin_required = FALSE //whether the gun can be fired without a pin
- var/obj/item/flashlight/gun_light
+ var/obj/item/flashlight/seclite/gun_light
var/can_flashlight = FALSE
var/gunlight_state = "flight"
var/obj/item/kitchen/knife/bayonet
@@ -115,23 +116,28 @@
QDEL_NULL(chambered)
return ..()
-/obj/item/gun/CheckParts(list/parts_list)
- ..()
- var/obj/item/gun/G = locate(/obj/item/gun) in contents
- if(G)
- G.forceMove(loc)
- QDEL_NULL(G.pin)
- visible_message("[G] can now fit a new pin, but the old one was destroyed in the process.", null, null, 3)
- qdel(src)
-
/obj/item/gun/examine(mob/user)
. = ..()
- if(no_pin_required)
- return
- if(pin)
- . += "It has \a [pin] installed."
- else
- . += "It doesn't have a firing pin installed, and won't fire."
+ if(!no_pin_required)
+ 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]."
+ else if(can_bayonet)
+ . += "It has a bayonet lug on it."
/obj/item/gun/equipped(mob/living/user, slot)
. = ..()
@@ -239,7 +245,7 @@
return
if(weapon_weight == WEAPON_HEAVY && user.get_inactive_held_item())
- to_chat(user, "You need both hands free to fire \the [src]!")
+ to_chat(user, "You need both hands free to fire [src]!")
return
user.DelayNextAction()
@@ -417,12 +423,12 @@
if(!gun_light)
if(!user.transferItemToLoc(I, src))
return
- to_chat(user, "You click \the [S] into place on \the [src].")
+ to_chat(user, "You click [S] into place on [src].")
if(S.on)
set_light(0)
- gun_light = S
+ set_gun_light(S)
update_gunlight(user)
- alight = new /datum/action/item_action/toggle_gunlight(src)
+ alight = new(src)
if(loc == user)
alight.Grant(user)
else if(istype(I, /obj/item/kitchen/knife))
@@ -431,27 +437,133 @@
return ..()
if(!user.transferItemToLoc(I, src))
return
- to_chat(user, "You attach \the [K] to the front of \the [src].")
+ to_chat(user, "You attach [K] to [src]'s bayonet lug.")
bayonet = K
update_icon()
- else if(I.tool_behaviour == TOOL_SCREWDRIVER)
- if(gun_light)
- var/obj/item/flashlight/seclite/S = gun_light
- to_chat(user, "You unscrew the seclite from \the [src].")
- gun_light = null
- S.forceMove(get_turf(user))
- update_gunlight(user)
- S.update_brightness(user)
- QDEL_NULL(alight)
- if(bayonet)
- to_chat(user, "You unscrew the bayonet from \the [src].")
- var/obj/item/kitchen/knife/K = bayonet
- K.forceMove(get_turf(user))
- bayonet = null
- update_icon()
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 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(span_warning("[user] attempts to remove [pin] from [src] with [I]."),
+ span_notice("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(span_notice("[pin] is pried out of [src] by [user], destroying the pin in the process."),
+ span_warning("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(span_warning("[user] attempts to remove [pin] from [src] with [I]."),
+ span_notice("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(span_notice("[pin] is spliced out of [src] by [user], melting part of the pin in the process."),
+ span_warning("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(span_warning("[user] attempts to remove [pin] from [src] with [I]."),
+ span_notice("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(span_notice("[pin] is ripped out of [src] by [user], mangling the pin in the process."),
+ span_warning("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, span_notice("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))
+ 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)
+ if(new_light.loc != src)
+ new_light.forceMove(src)
+
+ gun_light = new_light
+
/obj/item/gun/ui_action_click(mob/user, action)
if(istype(action, /datum/action/item_action/toggle_scope_zoom))
zoom(user, user.dir)
@@ -606,9 +718,16 @@
user.client.view_size.zoomIn()
/obj/item/gun/handle_atom_del(atom/A)
+ if(A == pin)
+ pin = null
if(A == chambered)
chambered = null
update_icon()
+ if(A == bayonet)
+ clear_bayonet()
+ if(A == gun_light)
+ clear_gunlight()
+ return ..()
/obj/item/gun/proc/getinaccuracy(mob/living/user, bonus_spread, stamloss)
return 0 // Replacement TBD: Exponential curved aim instability system.
@@ -642,3 +761,6 @@
. = recoil
if(user && !user.has_gravity())
. = recoil*5
+
+#undef FIRING_PIN_REMOVAL_DELAY
+#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER