mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-14 19:51:59 +00:00
* Energy gunfire sound pitch now varies based on how much ammo is left. Ballistic guns now have bolt sounds for low ammo. (#67549) About The Pull Request u0aw4DulWt.mp4 Why It's Good For The Game Breaks up the monotony of firearm sounds, makes them actually useful in a gameplay context. Energy gun effects inspired by Cruelty Squad. Changelog cl soundadd: Energy and Magic gunfire sound pitch now varies based on how much ammo is left. soundadd: Ballistic gunfire now has a low-ammo click sound. /cl * Energy gunfire sound pitch now varies based on how much ammo is left. Ballistic guns now have bolt sounds for low ammo. * mirror * Update gun.dm Co-authored-by: Iamgoofball <iamgoofball@gmail.com> Co-authored-by: John Doe <gamingskeleton3@gmail.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
702 lines
26 KiB
Plaintext
702 lines
26 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 = "pistol"
|
||
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 the gun will spawn loaded with a magazine
|
||
var/spawnwithmagazine = TRUE
|
||
///Compatible magazines with the gun
|
||
var/mag_type = /obj/item/ammo_box/magazine/m10mm //Removes the need for max_ammo and caliber info
|
||
///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 of the gun, affects quite a bit of functionality, 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 the gun can be sawn off by sawing tools
|
||
var/can_be_sawn_off = FALSE
|
||
var/flip_cooldown = 0
|
||
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
|
||
|
||
/obj/item/gun/ballistic/Initialize(mapload)
|
||
. = ..()
|
||
if (!spawnwithmagazine)
|
||
bolt_locked = TRUE
|
||
update_appearance()
|
||
return
|
||
if (!magazine)
|
||
magazine = new mag_type(src)
|
||
if(bolt_type == BOLT_TYPE_STANDARD)
|
||
chamber_round()
|
||
else
|
||
chamber_round(replace_new_round = TRUE)
|
||
update_appearance()
|
||
RegisterSignal(src, COMSIG_ITEM_RECHARGED, .proc/instant_reload)
|
||
|
||
/obj/item/gun/ballistic/add_weapon_description()
|
||
AddElement(/datum/element/weapon_description, attached_proc = .proc/add_notes_ballistic)
|
||
|
||
/obj/item/gun/ballistic/fire_sounds()
|
||
var/frequency_to_use = sin((90/magazine?.max_ammo) * get_ammo())
|
||
var/click_frequency_to_use = 1 - frequency_to_use * 0.75
|
||
var/play_click = round(sqrt(magazine?.max_ammo * 2)) > get_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)
|
||
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)
|
||
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
|
||
return "\nThe warning attached to the magazine is missing..."
|
||
|
||
/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(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)
|
||
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/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.
|
||
casing.bounce_away(TRUE)
|
||
SEND_SIGNAL(casing, COMSIG_CASING_EJECTED)
|
||
chambered = null
|
||
else if(empty_chamber)
|
||
chambered = null
|
||
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(keep_bullet = FALSE, spin_cylinder, replace_new_round)
|
||
if (chambered || !magazine)
|
||
return
|
||
if (magazine.ammo_count())
|
||
chambered = magazine.get_round(keep_bullet || bolt_type == BOLT_TYPE_NO_BOLT)
|
||
if (bolt_type != BOLT_TYPE_OPEN)
|
||
chambered.forceMove(src)
|
||
if(replace_new_round)
|
||
magazine.give_round(new chambered.type)
|
||
|
||
///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)
|
||
to_chat(user, span_notice("[src]'s [bolt_wording] is already cocked!"))
|
||
return
|
||
bolt_locked = FALSE
|
||
if (user)
|
||
to_chat(user, span_notice("You rack the [bolt_wording] of [src]."))
|
||
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)
|
||
to_chat(user, span_notice("You drop the [bolt_wording] of [src]."))
|
||
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, mag_type))
|
||
to_chat(user, span_warning("[AM] doesn't seem to fit into [src]..."))
|
||
return FALSE
|
||
if(user.transferItemToLoc(AM, src))
|
||
magazine = AM
|
||
if (display_message)
|
||
to_chat(user, span_notice("You load a new [magazine_wording] into [src]."))
|
||
playsound(src, load_empty_sound, load_sound_volume, load_sound_vary)
|
||
if (bolt_type == BOLT_TYPE_OPEN && !bolt_locked)
|
||
chamber_round(TRUE)
|
||
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, load_sound, load_sound_volume, load_sound_vary)
|
||
else
|
||
playsound(src, load_empty_sound, load_sound_volume, load_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))
|
||
to_chat(user, span_notice("You perform a tactical reload on [src]."))
|
||
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)
|
||
to_chat(user, span_notice("You pull the [magazine_wording] out of [src]."))
|
||
update_appearance()
|
||
|
||
SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) //SKYRAT EDIT ADDITION
|
||
|
||
/obj/item/gun/ballistic/can_shoot()
|
||
return chambered
|
||
|
||
/* SKYRAT EDIT REMOVAL MOVED TO MODULAR BALLISTIC_MASTER.DM
|
||
/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
|
||
to_chat(user, span_notice("There's already a [magazine_wording] in [src]."))
|
||
return
|
||
if (istype(A, /obj/item/ammo_casing) || istype(A, /obj/item/ammo_box))
|
||
if (bolt_type == BOLT_TYPE_NO_BOLT || internal_magazine)
|
||
if (chambered && !chambered.loaded_projectile)
|
||
chambered.forceMove(drop_location())
|
||
chambered = null
|
||
var/num_loaded = magazine?.attackby(A, user, params, TRUE)
|
||
if (num_loaded)
|
||
to_chat(user, span_notice("You load [num_loaded] [cartridge_wording]\s into [src]."))
|
||
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)
|
||
to_chat(user, span_warning("You can't seem to figure out how to fit [S] on [src]!"))
|
||
return
|
||
if(!user.is_holding(src))
|
||
to_chat(user, span_warning("You need be holding [src] to fit [S] to it!"))
|
||
return
|
||
if(suppressed)
|
||
to_chat(user, span_warning("[src] already has a suppressor!"))
|
||
return
|
||
if(user.transferItemToLoc(A, src))
|
||
to_chat(user, span_notice("You screw [S] onto [src]."))
|
||
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
|
||
*/ // SKYRAT EDIT END
|
||
/obj/item/gun/ballistic/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
|
||
|
||
if(magazine && chambered.loaded_projectile && can_misfire && misfire_probability > 0)
|
||
if(prob(misfire_probability))
|
||
if(blow_up(user))
|
||
to_chat(user, span_userdanger("[src] misfires!"))
|
||
|
||
if (sawn_off)
|
||
bonus_spread += SAWN_OFF_ACC_PENALTY
|
||
. = ..()
|
||
|
||
/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)
|
||
|
||
. = ..()
|
||
|
||
///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
|
||
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
|
||
w_class -= I.w_class
|
||
return ..()
|
||
|
||
/obj/item/gun/ballistic/AltClick(mob/user)
|
||
if (unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY))
|
||
reskin_obj(user)
|
||
return
|
||
if(loc == user)
|
||
if(suppressed && can_unsuppress)
|
||
var/obj/item/suppressor/S = suppressed
|
||
if(!user.is_holding(src))
|
||
return ..()
|
||
to_chat(user, span_notice("You unscrew [S] from [src]."))
|
||
user.put_in_hands(S)
|
||
clear_suppressor()
|
||
|
||
///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()
|
||
SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD)
|
||
|
||
///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)
|
||
bolt_locked = TRUE
|
||
update_appearance()
|
||
|
||
/obj/item/gun/ballistic/afterattack()
|
||
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(HAS_TRAIT(user, TRAIT_GUNFLIP))
|
||
SpinAnimation(4,2)
|
||
if(flip_cooldown <= world.time)
|
||
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
|
||
to_chat(user, span_userdanger("While trying to flip [src] you pull the trigger and accidentaly shoot yourself!"))
|
||
var/flip_mistake = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_CHEST)
|
||
process_fire(user, user, FALSE, flip_mistake)
|
||
user.dropItemToGround(src, TRUE)
|
||
return
|
||
flip_cooldown = (world.time + 30)
|
||
user.visible_message(span_notice("[user] spins [src] around [user.p_their()] finger by the trigger. That’s pretty badass."))
|
||
playsound(src, 'sound/items/handling/ammobox_pickup.ogg', 20, FALSE)
|
||
return
|
||
if(!internal_magazine && magazine)
|
||
if(!magazine.ammo_count())
|
||
eject_magazine(user)
|
||
return
|
||
if(bolt_type == BOLT_TYPE_NO_BOLT)
|
||
chambered = null
|
||
var/num_unloaded = 0
|
||
for(var/obj/item/ammo_casing/CB in get_ammo_list(FALSE, TRUE))
|
||
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)
|
||
to_chat(user, span_notice("You unload [num_unloaded] [cartridge_wording]\s from [src]."))
|
||
playsound(user, eject_sound, eject_sound_volume, eject_sound_vary)
|
||
update_appearance()
|
||
else
|
||
to_chat(user, span_warning("[src] is 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 attached that can be removed with <b>alt+click</b>."
|
||
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/boolets = 0 //mature var names for mature people
|
||
if (chambered && countchambered)
|
||
boolets++
|
||
if (magazine)
|
||
boolets += magazine.ammo_count()
|
||
return boolets
|
||
|
||
///gets a list of every bullet in the gun
|
||
/obj/item/gun/ballistic/proc/get_ammo_list(countchambered = TRUE, drop_all = FALSE)
|
||
var/list/rounds = list()
|
||
if(chambered && countchambered)
|
||
rounds.Add(chambered)
|
||
if(drop_all)
|
||
chambered = null
|
||
if(magazine)
|
||
rounds.Add(magazine.ammo_list(drop_all))
|
||
return rounds
|
||
|
||
#define BRAINS_BLOWN_THROW_RANGE 3
|
||
#define BRAINS_BLOWN_THROW_SPEED 1
|
||
/obj/item/gun/ballistic/suicide_act(mob/user)
|
||
var/obj/item/organ/internal/brain/B = user.getorganslot(ORGAN_SLOT_BRAIN)
|
||
if (B && chambered && chambered.loaded_projectile && can_trigger_gun(user) && !chambered.loaded_projectile.nodamage)
|
||
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(25)
|
||
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, turn(user.dir, 180), BRAINS_BLOWN_THROW_RANGE)
|
||
B.Remove(user)
|
||
B.forceMove(T)
|
||
var/datum/callback/gibspawner = CALLBACK(GLOBAL_PROC, /proc/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)
|
||
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)
|
||
to_chat(user, span_warning("[src] is already shortened!"))
|
||
return
|
||
if(bayonet)
|
||
to_chat(user, span_warning("You cannot saw-off [src] with [bayonet] attached!"))
|
||
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, 30, target = src))
|
||
if(sawn_off)
|
||
return
|
||
user.visible_message(span_notice("[user] shortens [src]!"), span_notice("You shorten [src]."))
|
||
name = "sawn-off [src.name]"
|
||
desc = sawn_desc
|
||
w_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
|
||
sawn_off = TRUE
|
||
update_appearance()
|
||
return TRUE
|
||
|
||
/obj/item/gun/ballistic/proc/guncleaning(mob/user, obj/item/A)
|
||
if(misfire_probability == 0)
|
||
to_chat(user, span_notice("[src] seems to be already clean of fouling."))
|
||
return
|
||
|
||
user.changeNext_move(CLICK_CD_MELEE)
|
||
user.visible_message(span_notice("[user] begins to cleaning [src]."), span_notice("You begin to clean the internals of [src]."))
|
||
|
||
if(do_after(user, 100, target = src))
|
||
var/original_misfire_value = initial(misfire_probability)
|
||
if(misfire_probability > original_misfire_value)
|
||
misfire_probability = original_misfire_value
|
||
user.visible_message(span_notice("[user] cleans [src] of any fouling."), span_notice("You clean [src], removing any fouling, preventing misfire."))
|
||
return TRUE
|
||
|
||
/obj/item/gun/ballistic/wrench_act(mob/living/user, obj/item/I)
|
||
if(!user.is_holding(src))
|
||
to_chat(user, span_notice("You need to hold [src] to modify it."))
|
||
return TRUE
|
||
|
||
if(!can_modify_ammo)
|
||
return
|
||
|
||
if(bolt_type == BOLT_TYPE_STANDARD)
|
||
if(get_ammo())
|
||
to_chat(user, span_notice("You can't get at the internals while the gun has a bullet in it!"))
|
||
return
|
||
|
||
else if(!bolt_locked)
|
||
to_chat(user, span_notice("You can't get at the internals while the bolt is down!"))
|
||
return
|
||
|
||
to_chat(user, span_notice("You begin to tinker with [src]..."))
|
||
I.play_tool_sound(src)
|
||
if(!I.use_tool(src, user, 3 SECONDS))
|
||
return TRUE
|
||
|
||
if(blow_up(user))
|
||
user.visible_message(span_danger("[src] goes off!"), span_danger("[src] goes off in your face!"))
|
||
return
|
||
|
||
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)
|
||
. = FALSE
|
||
for(var/obj/item/ammo_casing/AC in magazine.stored_ammo)
|
||
if(AC.loaded_projectile)
|
||
process_fire(user, user, FALSE)
|
||
. = TRUE
|
||
|
||
/obj/item/gun/ballistic/proc/instant_reload()
|
||
SIGNAL_HANDLER
|
||
if(magazine)
|
||
magazine.top_off()
|
||
else
|
||
if(!mag_type)
|
||
return
|
||
magazine = new mag_type(src)
|
||
chamber_round()
|
||
update_appearance()
|
||
|
||
/obj/item/suppressor
|
||
name = "suppressor"
|
||
desc = "A Scarborough Arms small-arms suppressor for maximum espionage." //SKYRAT EDIT
|
||
icon = 'icons/obj/guns/ballistic.dmi'
|
||
icon_state = "suppressor"
|
||
w_class = WEIGHT_CLASS_TINY
|
||
|
||
|
||
/obj/item/suppressor/specialoffer
|
||
name = "cheap suppressor"
|
||
desc = "A foreign knock-off suppressor, it feels flimsy, cheap, and brittle. Still fits most weapons."
|