Files
Bubberstation/code/modules/projectiles/guns/ballistic.dm
SmArtKar b359988016 Gun Refactor Part 1: Bayonet Componentization (#84246)
## About The Pull Request
The gun code is an absolute mess that seems to have been untouched for
the better part of the decade and finally gave way due to the attack
chain refactor. This PR is the first in my attempts to refactor this
mess by making bayonet attachment a component instead of /obj/item/gun
variables. Followup PRs may or may not be atomic changes or a monolith
due to how horribly the original code is structured.

## Why It's Good For The Game
Gun code is ancient, unmaintained, barely readable and started actively
breaking in the past weeks.

## Changelog
🆑
refactor: Bayonet attachment is now a component.
/🆑
2024-07-03 12:54:26 +00:00

744 lines
28 KiB
Plaintext

///Subtype for any kind of ballistic gun
///This has a shitload of vars on it, and I'm sorry for that, but it does make making new subtypes really easy
/obj/item/gun/ballistic
desc = "Now comes in flavors like GUN. Uses 10mm ammo, for some reason."
name = "projectile gun"
icon_state = "debug"
w_class = WEIGHT_CLASS_NORMAL
///sound when inserting magazine
var/load_sound = 'sound/weapons/gun/general/magazine_insert_full.ogg'
///sound when inserting an empty magazine
var/load_empty_sound = 'sound/weapons/gun/general/magazine_insert_empty.ogg'
///volume of loading sound
var/load_sound_volume = 40
///whether loading sound should vary
var/load_sound_vary = TRUE
///sound of racking
var/rack_sound = 'sound/weapons/gun/general/bolt_rack.ogg'
///volume of racking
var/rack_sound_volume = 60
///whether racking sound should vary
var/rack_sound_vary = TRUE
///sound of when the bolt is locked back manually
var/lock_back_sound = 'sound/weapons/gun/general/slide_lock_1.ogg'
///volume of lock back
var/lock_back_sound_volume = 60
///whether lock back varies
var/lock_back_sound_vary = TRUE
///Sound of ejecting a magazine
var/eject_sound = 'sound/weapons/gun/general/magazine_remove_full.ogg'
///sound of ejecting an empty magazine
var/eject_empty_sound = 'sound/weapons/gun/general/magazine_remove_empty.ogg'
///volume of ejecting a magazine
var/eject_sound_volume = 40
///whether eject sound should vary
var/eject_sound_vary = TRUE
///sound of dropping the bolt or releasing a slide
var/bolt_drop_sound = 'sound/weapons/gun/general/bolt_drop.ogg'
///volume of bolt drop/slide release
var/bolt_drop_sound_volume = 60
///empty alarm sound (if enabled)
var/empty_alarm_sound = 'sound/weapons/gun/general/empty_alarm.ogg'
///empty alarm volume sound
var/empty_alarm_volume = 70
///whether empty alarm sound varies
var/empty_alarm_vary = TRUE
///Whether our gun clicks when it approaches an empty magazine/chamber
var/click_on_low_ammo = TRUE
/// What type (includes subtypes) of magazine will this gun accept being put into it
var/obj/item/ammo_box/magazine/accepted_magazine_type = /obj/item/ammo_box/magazine/m10mm
/// Whether the gun will spawn loaded with a magazine
var/spawnwithmagazine = TRUE
/// Change this if the gun should spawn with a different magazine type to what accepted_magazine_type defines. Will create errors if not a type or subtype of accepted magazine.
var/obj/item/ammo_box/magazine/spawn_magazine_type
///Whether the sprite has a visible magazine or not
var/mag_display = TRUE
///Whether the sprite has a visible ammo display or not
var/mag_display_ammo = FALSE
///Whether the sprite has a visible indicator for being empty or not.
var/empty_indicator = FALSE
///Whether the gun alarms when empty or not.
var/empty_alarm = FALSE
///Whether the gun supports multiple special mag types
var/special_mags = FALSE
/**
* The bolt type controls how the gun functions, and what iconstates you'll need to represent those functions.
* BOLT_TYPE_STANDARD - The Slide doesn't lock back. Clicking on it will only cycle the bolt. Only 1 sprite.
* BOLT_TYPE_OPEN - Same as standard, but it fires directly from the magazine - No need to rack. Doesn't hold the bullet when you drop the mag.
* BOLT_TYPE_LOCKING - This is most handguns and bolt action rifles. The bolt will lock back when it's empty. You need yourgun_bolt and yourgun_bolt_locked icon states.
* BOLT_TYPE_NO_BOLT - This is shotguns and revolvers. clicking will dump out all the bullets in the gun, spent or not.
* see combat.dm defines for bolt types: BOLT_TYPE_STANDARD; BOLT_TYPE_LOCKING; BOLT_TYPE_OPEN; BOLT_TYPE_NO_BOLT
**/
var/bolt_type = BOLT_TYPE_STANDARD
///Used for locking bolt and open bolt guns. Set a bit differently for the two but prevents firing when true for both.
var/bolt_locked = FALSE
var/show_bolt_icon = TRUE ///Hides the bolt icon.
///Whether the gun has to be racked each shot or not.
var/semi_auto = TRUE
///Actual magazine currently contained within the gun
var/obj/item/ammo_box/magazine/magazine
///whether the gun ejects the chambered casing
var/casing_ejector = TRUE
///Whether the gun has an internal magazine or a detatchable one. Overridden by BOLT_TYPE_NO_BOLT.
var/internal_magazine = FALSE
///Phrasing of the bolt in examine and notification messages; ex: bolt, slide, etc.
var/bolt_wording = "bolt"
///Phrasing of the magazine in examine and notification messages; ex: magazine, box, etx
var/magazine_wording = "magazine"
///Phrasing of the cartridge in examine and notification messages; ex: bullet, shell, dart, etc.
var/cartridge_wording = "bullet"
///length between individual racks
var/rack_delay = 5
///time of the most recent rack, used for cooldown purposes
var/recent_rack = 0
///Whether the gun can be tacloaded by slapping a fresh magazine directly on it
var/tac_reloads = TRUE //Snowflake mechanic no more.
///Whether we need to hold the gun in our off-hand to load it. FALSE means we can load it literally anywhere. Important for weapons like bows.
var/must_hold_to_load = FALSE
///Whether the gun can be sawn off by sawing tools
var/can_be_sawn_off = FALSE
var/suppressor_x_offset ///pixel offset for the suppressor overlay on the x axis.
var/suppressor_y_offset ///pixel offset for the suppressor overlay on the y axis.
/// Check if you are able to see if a weapon has a bullet loaded in or not.
var/hidden_chambered = FALSE
// Gun internal magazine modification and misfiring
///Can we modify our ammo type in this gun's internal magazine?
var/can_modify_ammo = FALSE
///our initial ammo type. Should match initial caliber, but a bit of redundency doesn't hurt.
var/initial_caliber
///our alternative ammo type.
var/alternative_caliber
///our initial fire sound. same reasons for initial caliber
var/initial_fire_sound
///our alternative fire sound, in case we want our gun to be louder or quieter or whatever
var/alternative_fire_sound
///If only our alternative ammuntion misfires and not our main ammunition, we set this to TRUE
var/alternative_ammo_misfires = FALSE
/// Misfire Variables ///
/// Whether our ammo misfires now or when it's set by the wrench_act. TRUE means it misfires.
var/can_misfire = FALSE
///How likely is our gun to misfire?
var/misfire_probability = 0
///How much does shooting the gun increment the misfire probability?
var/misfire_percentage_increment = 0
///What is the cap on our misfire probability? Do not set this to 100.
var/misfire_probability_cap = 25
/// Fire Selector Variables ///
/// Tracks the firemode of burst weapons. TRUE means it is in burst mode.
var/burst_fire_selection = FALSE
/// If it has an icon for a selector switch indicating current firemode.
var/selector_switch_icon = FALSE
/obj/item/gun/ballistic/Initialize(mapload)
. = ..()
if(!spawn_magazine_type)
spawn_magazine_type = accepted_magazine_type
if (!spawnwithmagazine)
bolt_locked = TRUE
update_appearance()
return
if (!magazine)
magazine = new spawn_magazine_type(src)
if(!istype(magazine, accepted_magazine_type))
CRASH("[src] spawned with a magazine type that isn't allowed by its accepted_magazine_type!")
if(bolt_type == BOLT_TYPE_STANDARD || internal_magazine) //Internal magazines shouldn't get magazine + 1.
chamber_round()
else
chamber_round(replace_new_round = TRUE)
update_appearance()
RegisterSignal(src, COMSIG_ITEM_RECHARGED, PROC_REF(instant_reload))
/obj/item/gun/ballistic/Destroy()
QDEL_NULL(magazine)
return ..()
/obj/item/gun/ballistic/add_weapon_description()
AddElement(/datum/element/weapon_description, attached_proc = PROC_REF(add_notes_ballistic))
/obj/item/gun/ballistic/fire_sounds()
var/max_ammo = magazine?.max_ammo || initial(spawn_magazine_type.max_ammo)
var/current_ammo = get_ammo()
var/frequency_to_use = sin((90 / max_ammo) * current_ammo)
var/click_frequency_to_use = 1 - frequency_to_use * 0.75
var/play_click = round(sqrt(max_ammo * 2)) > current_ammo
if(suppressed)
playsound(src, suppressed_sound, suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0)
if(play_click && click_on_low_ammo)
playsound(src, 'sound/weapons/gun/general/ballistic_click.ogg', suppressed_volume, vary_fire_sound, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0, frequency = click_frequency_to_use)
else
playsound(src, fire_sound, fire_sound_volume, vary_fire_sound)
if(play_click && click_on_low_ammo)
playsound(src, 'sound/weapons/gun/general/ballistic_click.ogg', fire_sound_volume, vary_fire_sound, frequency = click_frequency_to_use)
/**
*
* Outputs type-specific weapon stats for ballistic weaponry based on its magazine and its caliber.
* It contains extra breaks for the sake of presentation
*
**/
/obj/item/gun/ballistic/proc/add_notes_ballistic()
if(magazine) // Make sure you have a magazine, to get the notes from
return "\n[magazine.add_notes_box()]"
else if(chambered) // if you don't have a magazine, is there something chambered?
return "\n[chambered.add_notes_ammo()]"
else // we have a very expensive mechanical paperweight.
return "\nThe lack of magazine and usable cartridge in chamber makes its usefulness questionable, at best."
/obj/item/gun/ballistic/vv_edit_var(vname, vval)
. = ..()
if(vname in list(NAMEOF(src, suppressor_x_offset), NAMEOF(src, suppressor_y_offset), NAMEOF(src, internal_magazine), NAMEOF(src, magazine), NAMEOF(src, chambered), NAMEOF(src, empty_indicator), NAMEOF(src, sawn_off), NAMEOF(src, bolt_locked), NAMEOF(src, bolt_type)))
update_appearance()
/obj/item/gun/ballistic/update_icon_state()
if(current_skin)
icon_state = "[unique_reskin[current_skin]][sawn_off ? "_sawn" : ""]"
else
icon_state = "[base_icon_state || initial(icon_state)][sawn_off ? "_sawn" : ""]"
return ..()
/obj/item/gun/ballistic/update_overlays()
. = ..()
if(selector_switch_icon)
switch(burst_fire_selection)
if(FALSE)
. += "[initial(icon_state)]_semi"
if(TRUE)
. += "[initial(icon_state)]_burst"
if(show_bolt_icon)
if (bolt_type == BOLT_TYPE_LOCKING)
. += "[icon_state]_bolt[bolt_locked ? "_locked" : ""]"
if (bolt_type == BOLT_TYPE_OPEN && bolt_locked)
. += "[icon_state]_bolt"
if(suppressed && can_unsuppress) // if it can't be unsuppressed, we assume the suppressor is integrated into the gun itself and don't generate an overlay
var/mutable_appearance/MA = mutable_appearance(icon, "[icon_state]_suppressor")
if(suppressor_x_offset)
MA.pixel_x = suppressor_x_offset
if(suppressor_y_offset)
MA.pixel_y = suppressor_y_offset
. += MA
if(!chambered && empty_indicator) //this is duplicated in c20's update_overlayss due to a layering issue with the select fire icon.
. += "[icon_state]_empty"
if(gun_flags & TOY_FIREARM_OVERLAY)
. += "[icon_state]_toy"
if(!magazine || internal_magazine || !mag_display)
return
if(special_mags)
. += "[icon_state]_mag_[initial(magazine.icon_state)]"
if(mag_display_ammo && !magazine.ammo_count())
. += "[icon_state]_mag_empty"
return
. += "[icon_state]_mag"
if(!mag_display_ammo)
return
var/capacity_number
switch(get_ammo() / magazine.max_ammo)
if(1 to INFINITY) //cause we can have one in the chamber.
capacity_number = 100
if(0.8 to 1)
capacity_number = 80
if(0.6 to 0.8)
capacity_number = 60
if(0.4 to 0.6)
capacity_number = 40
if(0.2 to 0.4)
capacity_number = 20
if(capacity_number)
. += "[icon_state]_mag_[capacity_number]"
/obj/item/gun/ballistic/ui_action_click(mob/user, actiontype)
if(istype(actiontype, /datum/action/item_action/toggle_firemode))
burst_select()
else
..()
/obj/item/gun/ballistic/proc/burst_select()
var/mob/living/carbon/human/user = usr
burst_fire_selection = !burst_fire_selection
if(!burst_fire_selection)
burst_size = 1
fire_delay = 0
balloon_alert(user, "switched to semi-automatic")
else
burst_size = initial(burst_size)
fire_delay = initial(fire_delay)
balloon_alert(user, "switched to [burst_size]-round burst")
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
update_appearance()
update_item_action_buttons()
/obj/item/gun/ballistic/handle_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
if(!semi_auto && from_firing)
return
var/obj/item/ammo_casing/casing = chambered //Find chambered round
if(istype(casing)) //there's a chambered round
if(QDELING(casing))
stack_trace("Trying to move a qdeleted casing of type [casing.type]!")
chambered = null
else if(casing_ejector || !from_firing)
casing.forceMove(drop_location()) //Eject casing onto ground.
if(!QDELETED(casing))
casing.bounce_away(TRUE)
SEND_SIGNAL(casing, COMSIG_CASING_EJECTED)
else if(empty_chamber)
clear_chambered()
if (chamber_next_round && (magazine?.max_ammo > 1))
chamber_round()
///Used to chamber a new round and eject the old one
/obj/item/gun/ballistic/proc/chamber_round(spin_cylinder, replace_new_round)
if (chambered || !magazine)
return
if (magazine.ammo_count())
chambered = magazine.get_round((bolt_type == BOLT_TYPE_OPEN && !bolt_locked) || bolt_type == BOLT_TYPE_NO_BOLT)
if (bolt_type != BOLT_TYPE_OPEN && !(internal_magazine && bolt_type == BOLT_TYPE_NO_BOLT))
chambered.forceMove(src)
else
RegisterSignal(chambered, COMSIG_MOVABLE_MOVED, PROC_REF(clear_chambered))
if(replace_new_round)
magazine.give_round(new chambered.type)
/obj/item/gun/ballistic/proc/clear_chambered(datum/source)
SIGNAL_HANDLER
UnregisterSignal(chambered, COMSIG_MOVABLE_MOVED)
chambered = null
///updates a bunch of racking related stuff and also handles the sound effects and the like
/obj/item/gun/ballistic/proc/rack(mob/user = null)
if (bolt_type == BOLT_TYPE_NO_BOLT) //If there's no bolt, nothing to rack
return
if (bolt_type == BOLT_TYPE_OPEN)
if(!bolt_locked) //If it's an open bolt, racking again would do nothing
if (user)
balloon_alert(user, "[bolt_wording] already cocked!")
return
bolt_locked = FALSE
if (user)
balloon_alert(user, "[bolt_wording] racked")
process_chamber(!chambered, FALSE)
if (bolt_type == BOLT_TYPE_LOCKING && !chambered)
bolt_locked = TRUE
playsound(src, lock_back_sound, lock_back_sound_volume, lock_back_sound_vary)
else
playsound(src, rack_sound, rack_sound_volume, rack_sound_vary)
update_appearance()
///Drops the bolt from a locked position
/obj/item/gun/ballistic/proc/drop_bolt(mob/user = null)
playsound(src, bolt_drop_sound, bolt_drop_sound_volume, FALSE)
if (user)
balloon_alert(user, "[bolt_wording] dropped")
chamber_round()
bolt_locked = FALSE
update_appearance()
///Handles all the logic needed for magazine insertion
/obj/item/gun/ballistic/proc/insert_magazine(mob/user, obj/item/ammo_box/magazine/AM, display_message = TRUE)
if(!istype(AM, accepted_magazine_type))
balloon_alert(user, "[AM.name] doesn't fit!")
return FALSE
if(user.transferItemToLoc(AM, src))
magazine = AM
if (display_message)
balloon_alert(user, "[magazine_wording] loaded")
if (magazine.ammo_count())
playsound(src, load_sound, load_sound_volume, load_sound_vary)
else
playsound(src, load_empty_sound, load_sound_volume, load_sound_vary)
if (bolt_type == BOLT_TYPE_OPEN && !bolt_locked)
chamber_round()
update_appearance()
return TRUE
else
to_chat(user, span_warning("You cannot seem to get [src] out of your hands!"))
return FALSE
///Handles all the logic of magazine ejection, if tac_load is set that magazine will be tacloaded in the place of the old eject
/obj/item/gun/ballistic/proc/eject_magazine(mob/user, display_message = TRUE, obj/item/ammo_box/magazine/tac_load = null)
if(bolt_type == BOLT_TYPE_OPEN)
chambered = null
if (magazine.ammo_count())
playsound(src, eject_sound, eject_sound_volume, eject_sound_vary)
else
playsound(src, eject_empty_sound, eject_sound_volume, eject_sound_vary)
magazine.forceMove(drop_location())
var/obj/item/ammo_box/magazine/old_mag = magazine
if (tac_load)
if (insert_magazine(user, tac_load, FALSE))
balloon_alert(user, "[magazine_wording] swapped")
else
to_chat(user, span_warning("You dropped the old [magazine_wording], but the new one doesn't fit. How embarassing."))
magazine = null
else
magazine = null
user.put_in_hands(old_mag)
old_mag.update_appearance()
if (display_message)
balloon_alert(user, "[magazine_wording] unloaded")
update_appearance()
/obj/item/gun/ballistic/can_shoot()
return chambered?.loaded_projectile
/obj/item/gun/ballistic/attackby(obj/item/A, mob/user, params)
. = ..()
if (.)
return
if (!internal_magazine && istype(A, /obj/item/ammo_box/magazine))
var/obj/item/ammo_box/magazine/AM = A
if (!magazine)
insert_magazine(user, AM)
else
if (tac_reloads)
eject_magazine(user, FALSE, AM)
else
balloon_alert(user, "already loaded!")
return
if (isammocasing(A) || istype(A, /obj/item/ammo_box))
if (must_hold_to_load && !check_if_held(user))
return
if (bolt_type == BOLT_TYPE_NO_BOLT || internal_magazine)
if (chambered && !chambered.loaded_projectile)
chambered.forceMove(drop_location())
if(chambered != magazine?.stored_ammo[1])
magazine.stored_ammo -= chambered
chambered = null
var/num_loaded = magazine?.attackby(A, user, params, TRUE)
if (num_loaded)
balloon_alert(user, "[num_loaded] [cartridge_wording]\s loaded")
playsound(src, load_sound, load_sound_volume, load_sound_vary)
if (chambered == null && bolt_type == BOLT_TYPE_NO_BOLT)
chamber_round()
A.update_appearance()
update_appearance()
return
if(istype(A, /obj/item/suppressor))
var/obj/item/suppressor/S = A
if(!can_suppress)
balloon_alert(user, "[S.name] doesn't fit!")
return
if(!user.is_holding(src))
balloon_alert(user, "not in hand!")
return
if(suppressed)
balloon_alert(user, "already has a supressor!")
return
if(user.transferItemToLoc(A, src))
balloon_alert(user, "[S.name] attached")
install_suppressor(A)
return
if (can_be_sawn_off)
if (sawoff(user, A))
return
if(can_misfire && istype(A, /obj/item/stack/sheet/cloth))
if(guncleaning(user, A))
return
return FALSE
/obj/item/gun/ballistic/proc/check_if_held(mob/user)
if(src != user.get_inactive_held_item())
return FALSE
return TRUE
/obj/item/gun/ballistic/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
if(target != user && chambered.loaded_projectile && can_misfire && prob(misfire_probability) && blow_up(user))
to_chat(user, span_userdanger("[src] misfires!"))
return
if (sawn_off)
bonus_spread += SAWN_OFF_ACC_PENALTY
return ..()
/obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
if(can_misfire)
misfire_probability += misfire_percentage_increment
misfire_probability = clamp(misfire_probability, 0, misfire_probability_cap)
return ..()
///Installs a new suppressor, assumes that the suppressor is already in the contents of src
/obj/item/gun/ballistic/proc/install_suppressor(obj/item/suppressor/S)
suppressed = S
update_weight_class(w_class + S.w_class) //so pistols do not fit in pockets when suppressed
update_appearance()
/obj/item/gun/ballistic/clear_suppressor()
if(!can_unsuppress)
return
if(isitem(suppressed))
var/obj/item/I = suppressed
update_weight_class(w_class - I.w_class)
return ..()
/obj/item/gun/ballistic/click_alt(mob/user)
if(!suppressed || !can_unsuppress)
return CLICK_ACTION_BLOCKING
var/obj/item/suppressor/S = suppressed
if(!user.is_holding(src))
return CLICK_ACTION_BLOCKING
balloon_alert(user, "[S.name] removed")
user.put_in_hands(S)
clear_suppressor()
return CLICK_ACTION_SUCCESS
///Prefire empty checks for the bolt drop
/obj/item/gun/ballistic/proc/prefire_empty_checks()
if (!chambered && !get_ammo())
if (bolt_type == BOLT_TYPE_OPEN && !bolt_locked)
bolt_locked = TRUE
playsound(src, bolt_drop_sound, bolt_drop_sound_volume)
update_appearance()
///postfire empty checks for bolt locking and sound alarms
/obj/item/gun/ballistic/proc/postfire_empty_checks(last_shot_succeeded)
if (!chambered && !get_ammo())
if (empty_alarm && last_shot_succeeded)
playsound(src, empty_alarm_sound, empty_alarm_volume, empty_alarm_vary)
update_appearance()
if (last_shot_succeeded && bolt_type == BOLT_TYPE_LOCKING && semi_auto)
bolt_locked = TRUE
update_appearance()
/obj/item/gun/ballistic/fire_gun(atom/target, mob/living/user, flag, params)
prefire_empty_checks()
. = ..() //The gun actually firing
postfire_empty_checks(.)
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/gun/ballistic/attack_hand(mob/user, list/modifiers)
if(!internal_magazine && loc == user && user.is_holding(src) && magazine)
eject_magazine(user)
return
return ..()
/obj/item/gun/ballistic/attack_self(mob/living/user)
if(!internal_magazine && magazine)
if(!magazine.ammo_count())
eject_magazine(user)
return
if(bolt_type == BOLT_TYPE_NO_BOLT)
var/num_unloaded = 0
for(var/obj/item/ammo_casing/CB as anything in get_ammo_list(FALSE))
CB.forceMove(drop_location())
CB.bounce_away(FALSE, NONE)
num_unloaded++
var/turf/T = get_turf(drop_location())
if(T && is_station_level(T.z))
SSblackbox.record_feedback("tally", "station_mess_created", 1, CB.name)
if (num_unloaded)
balloon_alert(user, "[num_unloaded] [cartridge_wording]\s unloaded")
playsound(user, eject_sound, eject_sound_volume, eject_sound_vary)
update_appearance()
else
balloon_alert(user, "it's empty!")
return
if(bolt_type == BOLT_TYPE_LOCKING && bolt_locked)
drop_bolt(user)
return
if (recent_rack > world.time)
return
recent_rack = world.time + rack_delay
rack(user)
return
/obj/item/gun/ballistic/examine(mob/user)
. = ..()
var/count_chambered = !(bolt_type == BOLT_TYPE_NO_BOLT || bolt_type == BOLT_TYPE_OPEN)
. += "It has [get_ammo(count_chambered)] round\s remaining."
if (!chambered && !hidden_chambered)
. += "It does not seem to have a round chambered."
if (bolt_locked)
. += "The [bolt_wording] is locked back and needs to be released before firing or de-fouling."
if (suppressed)
. += "It has a suppressor [can_unsuppress ? "attached that can be removed with <b>alt+click</b>." : "that is integral or can't otherwise be removed."]"
if(can_misfire)
. += span_danger("You get the feeling this might explode if you fire it...")
if(misfire_probability > 0)
. += span_danger("Given the state of the gun, there is a [misfire_probability]% chance it'll misfire.")
///Gets the number of bullets in the gun
/obj/item/gun/ballistic/proc/get_ammo(countchambered = TRUE)
var/bullets = 0 //No silly variable names on my watch.
if (chambered && countchambered)
bullets++
if (magazine)
bullets += magazine.ammo_count()
return bullets
///gets a list of every bullet in the gun
/obj/item/gun/ballistic/proc/get_ammo_list(countchambered = TRUE)
var/list/rounds = list()
if(chambered && countchambered)
rounds.Add(chambered)
if(magazine)
rounds.Add(magazine.ammo_list())
return rounds
#define BRAINS_BLOWN_THROW_RANGE 3
#define BRAINS_BLOWN_THROW_SPEED 1
/obj/item/gun/ballistic/suicide_act(mob/living/user)
var/obj/item/organ/internal/brain/B = user.get_organ_slot(ORGAN_SLOT_BRAIN)
if (B && chambered && chambered.loaded_projectile && can_trigger_gun(user) && chambered.loaded_projectile.damage > 0)
user.visible_message(span_suicide("[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!"))
sleep(2.5 SECONDS)
if(user.is_holding(src))
var/turf/T = get_turf(user)
process_fire(user, user, FALSE, null, BODY_ZONE_HEAD)
user.visible_message(span_suicide("[user] blows [user.p_their()] brain[user.p_s()] out with [src]!"))
var/turf/target = get_ranged_target_turf(user, REVERSE_DIR(user.dir), BRAINS_BLOWN_THROW_RANGE)
B.Remove(user)
B.forceMove(T)
var/datum/callback/gibspawner = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(spawn_atom_to_turf), /obj/effect/gibspawner/generic, B, 1, FALSE, user)
B.throw_at(target, BRAINS_BLOWN_THROW_RANGE, BRAINS_BLOWN_THROW_SPEED, callback=gibspawner)
return BRUTELOSS
else
user.visible_message(span_suicide("[user] panics and starts choking to death!"))
return OXYLOSS
else
user.visible_message(span_suicide("[user] is pretending to blow [user.p_their()] brain[user.p_s()] out with [src]! It looks like [user.p_theyre()] trying to commit suicide!</b>"))
playsound(src, dry_fire_sound, 30, TRUE)
return OXYLOSS
#undef BRAINS_BLOWN_THROW_SPEED
#undef BRAINS_BLOWN_THROW_RANGE
GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
/obj/item/gun/energy/plasmacutter,
/obj/item/melee/energy,
/obj/item/dualsaber
)))
///Handles all the logic of sawing off guns,
/obj/item/gun/ballistic/proc/sawoff(mob/user, obj/item/saw, handle_modifications = TRUE)
if(!saw.get_sharpness() || (!is_type_in_typecache(saw, GLOB.gun_saw_types) && saw.tool_behaviour != TOOL_SAW)) //needs to be sharp. Otherwise turned off eswords can cut this.
return
if(sawn_off)
balloon_alert(user, "it's already shortened!")
return
if (SEND_SIGNAL(src, COMSIG_GUN_BEING_SAWNOFF, user) & COMPONENT_CANCEL_SAWING_OFF)
return
user.changeNext_move(CLICK_CD_MELEE)
user.visible_message(span_notice("[user] begins to shorten [src]."), span_notice("You begin to shorten [src]..."))
//if there's any live ammo inside the gun, makes it go off
if(blow_up(user))
user.visible_message(span_danger("[src] goes off!"), span_danger("[src] goes off in your face!"))
return
if(!do_after(user, 3 SECONDS, target = src))
return
if(sawn_off)
return
user.visible_message(span_notice("[user] shortens [src]!"), span_notice("You shorten [src]."))
sawn_off = TRUE
SEND_SIGNAL(src, COMSIG_GUN_SAWN_OFF)
if(!handle_modifications)
return TRUE
name = "sawn-off [src.name]"
desc = sawn_desc
update_weight_class(WEIGHT_CLASS_NORMAL)
//The file might not have a "gun" icon, let's prepare for this
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
inhand_x_dimension = 32
inhand_y_dimension = 32
inhand_icon_state = "gun"
worn_icon_state = "gun"
slot_flags &= ~ITEM_SLOT_BACK //you can't sling it on your back
slot_flags |= ITEM_SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally)
recoil = SAWN_OFF_RECOIL
update_appearance()
return TRUE
/obj/item/gun/ballistic/proc/guncleaning(mob/user, obj/item/A)
if(misfire_probability == initial(misfire_probability))
balloon_alert(user, "it's already clean!")
return
user.changeNext_move(CLICK_CD_MELEE)
balloon_alert(user, "cleaning...")
if(do_after(user, 10 SECONDS, target = src))
misfire_probability = initial(misfire_probability)
balloon_alert(user, "fouling cleaned out")
return TRUE
/obj/item/gun/ballistic/wrench_act(mob/living/user, obj/item/I)
if(!can_modify_ammo)
return
if(!user.is_holding(src))
balloon_alert(user, "hold to modify!")
return TRUE
if(get_ammo())
balloon_alert(user, "can't modify while loaded!")
return
if(!bolt_locked && bolt_type == BOLT_TYPE_LOCKING)
balloon_alert(user, "the bolt is in the way!")
return
balloon_alert(user, "tinkering...")
I.play_tool_sound(src)
if(!I.use_tool(src, user, 3 SECONDS))
return TRUE
if(magazine.caliber == initial_caliber)
magazine.caliber = alternative_caliber
if(alternative_ammo_misfires)
can_misfire = TRUE
fire_sound = alternative_fire_sound
to_chat(user, span_notice("You modify [src]. Now it will fire [alternative_caliber] rounds."))
else
magazine.caliber = initial_caliber
if(alternative_ammo_misfires)
can_misfire = FALSE
fire_sound = initial_fire_sound
to_chat(user, span_notice("You reset [src]. Now it will fire [initial_caliber] rounds."))
///used for sawing guns, causes the gun to fire without the input of the user
/obj/item/gun/ballistic/proc/blow_up(mob/user)
return chambered && process_fire(user, user, FALSE)
/obj/item/gun/ballistic/proc/instant_reload()
SIGNAL_HANDLER
if(magazine)
magazine.top_off()
else
if(!spawn_magazine_type)
return
magazine = new spawn_magazine_type(src)
chamber_round()
update_appearance()
/obj/item/suppressor
name = "suppressor"
desc = "A syndicate small-arms suppressor for maximum espionage."
icon = 'icons/obj/weapons/guns/ballistic.dmi'
icon_state = "suppressor"
w_class = WEIGHT_CLASS_TINY