Firearms rework again [Modular Attachments] (#16028)

* initial modular attachment commit

* fix bitflags

* Update code/modules/projectiles/gun.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/modules/projectiles/attachments/laser_sight.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/modules/projectiles/attachments/_attachment.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/datums/action.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/datums/action.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/datums/action.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/datums/action.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/modules/projectiles/attachments/_attachment.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* Update code/modules/projectiles/attachments/_attachment.dm

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>

* use better names

* fix tatax breaking my pr

Co-authored-by: tattax <71668564+tattax@users.noreply.github.com>
This commit is contained in:
ynot01
2022-10-08 09:03:11 -05:00
committed by GitHub
parent 094e2ed99e
commit 240bb4c06f
15 changed files with 533 additions and 14 deletions

View File

@@ -0,0 +1,107 @@
// Attachment space bitflags.
// Attachments that are not exclusive i.e. attaches to the barrel should have an attachment_type of 0.
#define TYPE_SIGHT (1<<0) // Scopes, sights
#define TYPE_BARREL (1<<1) // Shorter, longer, or otherwise modified barrel
#define TYPE_TRIGGER (1<<2) // Modified trigger
#define TYPE_FOREGRIP (1<<3) // Foregrip
/// Base attachment.
/// "_a" icons should be 5x5 pixels.
/// See icons/obj/guns/attachment.dmi.
/obj/item/attachment
name = "attachment"
desc = "It's an attachment."
icon = 'icons/obj/guns/attachment.dmi'
/// Attached sprite adds "_a" e.g. "iconname_a"
icon_state = "_error"
var/obj/item/gun/attached_gun
/// If the attachment can be "turned on", it will use "_on" e.g. "iconname_on_a" and "iconname_on".
/// It is important to note that not all attachments can be turned on, so you don't have to worry about this most of the time.
var/is_on = FALSE
/// Attachments that are not exclusive i.e. attaches to the side of the barrel should have an attachment_type of 0.
/// Otherwise, use one or many bitflags to represent the exclusive space this attachment should occuy.
var/attachment_type = 0
/// "You slide the attachment into place on gun."
var/attach_verb = "slide"
var/mob/current_user = null
/// List of actions to add to the gun when attached.
/// See code/modules/projectiles/attachments/laser_sight.dm for example.
var/list/actions_list = list()
/obj/item/attachment/update_icon()
icon_state = initial(icon_state) + is_on ? "_on" : ""
. = ..()
attached_gun?.update_attachments()
/obj/item/attachment/Destroy()
if(attached_gun)
on_detach(attached_gun)
. = ..()
/// Called when the attacment is attached to a weapon
/obj/item/attachment/proc/on_attach(obj/item/gun/G, mob/user = null)
attached_gun = G
for(var/act in actions_list)
var/datum/action/attachment_action = new act(G)
G.attachment_actions += attachment_action
if(user && G.loc == user)
attachment_action.Grant(user)
if(G.loc == user)
set_user(user)
G.attachment_flags |= attachment_type
G.current_attachments += src
G.update_attachments()
forceMove(G)
if(user)
current_user = user
/// Called when the attachment is detached from a weapon
/obj/item/attachment/proc/on_detach(obj/item/gun/G, mob/living/user = null)
for(var/act_type in actions_list)
for(var/stored_attachment in G.attachment_actions)
if(istype(stored_attachment, act_type))
var/datum/action/typed_attachment = stored_attachment
typed_attachment.Remove(user)
G.attachment_actions -= stored_attachment
QDEL_NULL(stored_attachment)
break
attached_gun = null
set_user()
G.attachment_flags ^= attachment_type
G.current_attachments -= src
G.update_attachments()
if(user)
user.put_in_hands(src)
else
forceMove(get_turf(G))
/obj/item/attachment/proc/on_gun_fire(obj/item/gun/G)
/obj/item/attachment/proc/set_user(mob/user = null)
if(user == current_user)
return
if(istype(current_user))
current_user = null
if(istype(user))
current_user = user
/obj/item/attachment/proc/check_user()
if(!istype(current_user))
if(ismob(loc))
set_user(loc)
else if(ismob(loc.loc))
set_user(loc.loc)
if(!istype(current_user) || !isturf(current_user.loc) || !( (src in current_user.held_items)||(loc in current_user.held_items) ) || current_user.incapacitated()) //Doesn't work if you're not holding it!
return FALSE
return TRUE

View File

@@ -0,0 +1,20 @@
/// Base grip
/obj/item/attachment/grip
name = "grip"
desc = "It's a grip."
attachment_type = TYPE_FOREGRIP
var/steady = 0
/obj/item/attachment/grip/on_attach(obj/item/gun/G, mob/user = null)
. = ..()
G.recoil -= steady
/obj/item/attachment/grip/on_detach(obj/item/gun/G, mob/living/user = null)
. = ..()
G.recoil += steady
/obj/item/attachment/grip/vertical
name = "vertical grip"
desc = "A tactile grip that increases the control and steadiness of your weapon."
icon_state = "vert_grip"
steady = 0.5

View File

@@ -0,0 +1,139 @@
/obj/item/attachment/laser_sight
name = "laser sight"
desc = "A glorified laser pointer. Good for knowing what you're aiming at."
icon_state = "laser_sight"
var/lastangle = 0
var/aiming_lastangle = 0
var/aiming_time = 12
var/aiming_time_left = 12
var/laser_color = rgb(255,0,0)
var/listeningTo = null
var/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/last_beam
actions_list = list(/datum/action/item_action/toggle_laser_sight, /datum/action/item_action/change_laser_sight_color)
/obj/item/attachment/laser_sight/examine(mob/user)
. = ..()
. += span_notice("You can <b>alt-click</b> it to change its color.")
/obj/item/attachment/laser_sight/AltClick(mob/user)
. = ..()
var/C = input(user, "Select Laser Color", "Select laser color", laser_color) as null|color
if(!C || QDELETED(src))
return
laser_color = C
/obj/item/attachment/laser_sight/on_attach(obj/item/gun/G, mob/user = null)
. = ..()
START_PROCESSING(SSfastprocess, src)
if(is_on)
G.spread -= 6
/obj/item/attachment/laser_sight/on_detach(obj/item/gun/G, mob/living/user = null)
. = ..()
STOP_PROCESSING(SSfastprocess, src)
if(is_on)
G.spread += 6
QDEL_LIST(attached_gun.current_tracers)
/obj/item/attachment/laser_sight/attack_self(mob/user)
. = ..()
toggle_on()
/obj/item/attachment/laser_sight/proc/toggle_on()
is_on = !is_on
playsound(loc, is_on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1)
if(attached_gun)
if(is_on)
attached_gun.spread -= 6
else
attached_gun.spread += 6
QDEL_LIST(attached_gun.current_tracers)
update_icon()
/obj/item/attachment/laser_sight/process()
return aiming_beam(TRUE)
/obj/item/attachment/laser_sight/proc/aiming_beam(force_update = FALSE)
if(!is_on)
return
if(!attached_gun)
return PROCESS_KILL
if(!check_user())
// Doesn't need to be optimized, runs nothing if the list is already empty
QDEL_LIST(attached_gun.current_tracers)
return
process_aim()
var/diff = abs(aiming_lastangle - lastangle)
if(diff < 0.1 && !force_update)
return
aiming_lastangle = lastangle
var/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new
P.gun = attached_gun
P.color = laser_color
var/turf/curloc = get_turf(src)
var/turf/targloc = get_turf(current_user.client.mouseObject)
if(!istype(targloc))
if(!istype(curloc))
return
targloc = get_turf_in_angle(lastangle, curloc, 10)
P.preparePixelProjectile(targloc, current_user, current_user.client.mouseParams, 0)
P.fire(lastangle)
last_beam = P
/obj/item/attachment/laser_sight/equipped(mob/user)
set_user(user)
return ..()
/obj/item/attachment/laser_sight/pickup(mob/user)
set_user(user)
return ..()
/obj/item/attachment/laser_sight/dropped(mob/user)
set_user()
return ..()
/obj/item/attachment/laser_sight/set_user(mob/user = null)
if(user == current_user)
return
aiming_time_left = aiming_time
if(listeningTo)
UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
listeningTo = null
if(istype(current_user))
LAZYREMOVE(current_user.mousemove_intercept_objects, src)
current_user = null
if(istype(user))
current_user = user
LAZYOR(current_user.mousemove_intercept_objects, src)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/on_mob_move)
listeningTo = user
/obj/item/attachment/laser_sight/proc/on_mob_move()
if(!check_user())
return
if(is_on)
delay_penalty(3)
aiming_beam(TRUE)
/obj/item/attachment/laser_sight/onMouseMove(object, location, control, params)
. = ..()
if(is_on)
aiming_beam()
/obj/item/attachment/laser_sight/proc/process_aim()
if(istype(current_user) && current_user.client && current_user.client.mouseParams)
var/angle = mouse_angle_from_client(current_user.client)
current_user.setDir(angle2dir_cardinal(angle))
var/difference = abs(closer_angle_difference(lastangle, angle))
delay_penalty(difference * 0.3)
lastangle = angle
/obj/item/attachment/laser_sight/proc/delay_penalty(amount)
aiming_time_left = clamp(aiming_time_left + amount, 0, aiming_time)
/obj/item/attachment/laser_sight/Destroy()
set_user(null)
listeningTo = null
STOP_PROCESSING(SSfastprocess, src)
QDEL_LIST(attached_gun?.current_tracers)
return ..()

View File

@@ -0,0 +1,26 @@
/// Base sight
/obj/item/attachment/scope
name = "sight"
desc = "It's a sight."
attachment_type = TYPE_SIGHT
var/accuracy = 0
/obj/item/attachment/scope/on_attach(obj/item/gun/G, mob/user = null)
. = ..()
G.spread -= accuracy
/obj/item/attachment/scope/on_detach(obj/item/gun/G, mob/living/user = null)
. = ..()
G.spread += accuracy
/obj/item/attachment/scope/simple
name = "simple sight"
desc = "A simple yet elegant scope. Better than ironsights."
icon_state = "simple_sight"
accuracy = 3
/obj/item/attachment/scope/holo
name = "holographic sight"
desc = "A highly advanced sight that projects a holographic design onto its lens, providing unobscured and precise view of your target."
icon_state = "holo_sight"
accuracy = 6

View File

@@ -29,10 +29,10 @@
var/suppressed_sound = 'sound/weapons/gunshot_silenced.ogg'
var/suppressed_volume = 10
var/can_unsuppress = TRUE
var/recoil = 0 //boom boom shake the room
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
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
@@ -40,7 +40,7 @@
var/firing_burst = 0 //Prevent the weapon from firing again while already firing
var/semicd = 0 //cooldown handler
var/weapon_weight = WEAPON_LIGHT
var/spread = 0 //Spread induced by the gun itself.
var/spread = 5 //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'
@@ -60,6 +60,14 @@
var/knife_x_offset = 0
var/knife_y_offset = 0
var/list/available_attachments = list() // What attachments can this gun have
var/max_attachments = 0 // How many attachments can this gun hold, recommend not going over 5
var/list/current_attachments = list()
var/list/attachment_overlays = list()
var/attachment_flags = 0
var/attachment_actions = list()
var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite
var/ammo_y_offset = 0
var/flight_x_offset = 0
@@ -73,6 +81,8 @@
var/datum/action/toggle_scope_zoom/azoom
var/recent_shoot = null //time of the last shot with the gun. Used to track if firing happened for feedback out of all things
var/list/obj/effect/projectile/tracer/current_tracers
/obj/item/gun/Initialize()
. = ..()
if(pin)
@@ -82,6 +92,7 @@
pin = new pin(src)
if(gun_light)
alight = new(src)
current_tracers = list()
build_zooming()
/obj/item/gun/Destroy()
@@ -112,6 +123,10 @@
clear_bayonet()
if(A == gun_light)
clear_gunlight()
if(A in current_attachments)
var/obj/item/attachment/T = A
T.on_detach(src)
return ..()
/obj/item/gun/CheckParts(list/parts_list)
@@ -148,9 +163,14 @@
. += span_info("[bayonet] looks like it can be <b>unscrewed</b> from [src].")
else if(can_bayonet)
. += "It has a <b>bayonet</b> lug on it."
for(var/obj/item/attachment/A in current_attachments)
. += "It has \a [A] affixed to it."
/obj/item/gun/equipped(mob/living/user, slot)
. = ..()
for(var/obj/item/attachment/A in current_attachments)
A.set_user(user)
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!!
@@ -169,7 +189,7 @@
/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
if(recoil)
if(recoil > 0)
shake_camera(user, recoil + 1, recoil)
if(suppressed)
@@ -331,6 +351,9 @@
if(user)
SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override)
for(var/obj/item/attachment/A in current_attachments)
A.on_gun_fire(src)
add_fingerprint(user)
if(semicd)
@@ -339,7 +362,7 @@
var/sprd = 0
var/randomized_gun_spread = 0
var/rand_spr = rand()
if(spread)
if(spread > 0)
randomized_gun_spread = rand(0,spread)
if(ishuman(user)) //nice shootin' tex
var/mob/living/carbon/human/H = user
@@ -406,6 +429,41 @@
/obj/item/gun/attackby(obj/item/I, mob/user, params)
if(user.a_intent == INTENT_HARM)
return ..()
else if (istype(I, /obj/item/attachment))
var/support = FALSE
for(var/n in available_attachments)
if(istype(I, n))
support = TRUE
break
if(!support)
to_chat(user, span_warning("\The [src] does not support \the [I]!"))
return ..()
var/already_has = FALSE
for(var/n in current_attachments)
if(istype(I, n))
already_has = TRUE
break
if(already_has)
to_chat(user, span_warning("\The [src] already has \a [I]!"))
return ..()
if(LAZYLEN(current_attachments) >= max_attachments)
to_chat(user, span_warning("\The [src] has no more room for any more attachments!"))
return ..()
var/obj/item/attachment/A = I
if(A.attachment_type != 0 && ((attachment_flags &= A.attachment_type) != 0))
to_chat(user, span_warning("\The [src] does not have any available places to attach \the [I] onto!"))
return ..()
to_chat(user, span_notice("You [A.attach_verb] \the [I] into place on [src]."))
A.on_attach(src, user)
else if(istype(I, /obj/item/flashlight/seclite))
if(!can_flashlight)
return ..()
@@ -446,17 +504,31 @@
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/has_fl = FALSE
var/has_bayo = FALSE
var/amt_modular = LAZYLEN(current_attachments)
if(can_flashlight && gun_light)
has_fl = TRUE
if(bayonet && can_bayonet)
has_bayo = TRUE
var/attachments_amt = amt_modular + has_fl + has_bayo
if(attachments_amt > 1) //give them a choice instead of removing both
var/list/possible_items = list(gun_light, bayonet)
possible_items += current_attachments
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.
else if(amt_modular == 1)
return remove_gun_attachment(user, I, current_attachments[1], "unscrewed")
else if(has_fl) //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
else if(has_bayo) //if it has a bayonet, and the bayonet can be removed
return remove_gun_attachment(user, I, bayonet, "unfix")
/obj/item/gun/proc/remove_gun_attachment(mob/living/user, obj/item/tool_item, obj/item/item_to_remove, removal_verb)
@@ -468,6 +540,10 @@
if(Adjacent(user) && !issilicon(user))
user.put_in_hands(item_to_remove)
if(istype(item_to_remove, /obj/item/attachment))
var/obj/item/attachment/A = item_to_remove
return A.on_detach(src, user)
if(item_to_remove == bayonet)
return clear_bayonet()
else if(item_to_remove == gun_light)
@@ -532,13 +608,38 @@
var/datum/action/A = X
A.UpdateButtonIcon()
/obj/item/gun/proc/update_attachments()
for(var/mutable_appearance/M in attachment_overlays)
cut_overlay(M, TRUE)
attachment_overlays = list()
var/att_position = 0
for(var/obj/item/attachment/A in current_attachments)
var/mutable_appearance/M = mutable_appearance('icons/obj/guns/attachment.dmi', "[A.icon_state]_a")
M.pixel_x = att_position * 6
add_overlay(M, TRUE)
attachment_overlays += M
att_position += 1
update_icon(TRUE)
for(var/datum/action/A as anything in actions)
A.UpdateButtonIcon()
/obj/item/gun/pickup(mob/user)
..()
for(var/obj/item/attachment/A in current_attachments)
A.set_user(user)
for(var/datum/action/att_act in attachment_actions)
att_act.Grant(user)
if(azoom)
azoom.Grant(user)
/obj/item/gun/dropped(mob/user)
. = ..()
for(var/obj/item/attachment/A in current_attachments)
A.set_user()
for(var/datum/action/att_act in attachment_actions)
att_act.Remove(user)
if(azoom)
azoom.Remove(user)
if(zoomed)

View File

@@ -110,6 +110,15 @@
var/feedback_recoil_reverse = FALSE // TRUE for clockwise , FALSE for anti-clockwise
var/feedback_slide_close_move = TRUE // does the slide closing cause the gun to twist clockwise?
available_attachments = list(
/obj/item/attachment/scope/simple,
/obj/item/attachment/scope/holo,
/obj/item/attachment/laser_sight,
/obj/item/attachment/grip/vertical,
)
max_attachments = 4
recoil = 0.3
/obj/item/gun/ballistic/proc/feedback(type) // checks to see if gun has that feedback type enabled then commences the animation
if(feedback_types[type])
feedback_commence(type, feedback_types[type])

View File

@@ -22,6 +22,15 @@
var/use_cyborg_cell = FALSE //whether the gun's cell drains the cyborg user's cell to recharge
var/dead_cell = FALSE //set to true so the gun is given an empty cell
available_attachments = list(
/obj/item/attachment/scope/simple,
/obj/item/attachment/scope/holo,
/obj/item/attachment/laser_sight,
/obj/item/attachment/grip/vertical,
)
max_attachments = 4
recoil = 0.1
/obj/item/gun/energy/emp_act(severity)
. = ..()
if(!(. & EMP_PROTECT_CONTENTS))

View File

@@ -17,6 +17,8 @@
var/can_charge = TRUE
var/ammo_type
var/no_den_usage
recoil = 0
spread = 0
clumsy_check = 0
trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses magic instead
pin = /obj/item/firing_pin/magic

View File

@@ -42,7 +42,6 @@
var/lastangle = 0
var/aiming_lastangle = 0
var/mob/current_user = null
var/list/obj/effect/projectile/tracer/current_tracers
var/structure_piercing = 2 //Amount * 2. For some reason structures aren't respecting this unless you have it doubled. Probably with the objects in question's Bump() code instead of this but I'll deal with this later.
var/structure_bleed_coeff = 0.7
@@ -170,7 +169,6 @@
/obj/item/gun/energy/beam_rifle/Initialize()
. = ..()
fire_delay = delay
current_tracers = list()
START_PROCESSING(SSfastprocess, src)
zoom_lock_action = new(src)
@@ -425,7 +423,7 @@
flag = ENERGY
range = 150
jitter = 10
var/obj/item/gun/energy/beam_rifle/gun
var/obj/item/gun/gun
var/structure_pierce_amount = 0 //All set to 0 so the gun can manually set them during firing.
var/structure_bleed_coeff = 0
var/structure_pierce = 0