mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-11 01:13:18 +00:00
This PR kills off the transforming subtype of /obj/item/melee and replaces it with a component to handle the transforming behavior, /datum/component/transforming.
The transforming component handles updating the variables of an item when it's transformed. Things like force, sharpness, whetstone force bonus, and attack verbs. Similar to the two-handed component, but instead of transforming into a two-hander it remains a one handed weapon.
The "nemesis" behavior (dealing addition damage to certain factions) of the transforming subtype was moved to the cleaving saw only, since it was the only transforming item that used it. In the future, this can be made into a bespoke element/component as well.
The following weapons and items have been updated to use this component:
Energy Swords / Sabers / Bananium Energy Sword
Energy Circular Saw
Energy Dagger
Energy Axe
Toy Energy Sword
Holographic Energy Sword
Switchblade
Advanced Medical Tools (Laser scalpel, Mechanical Pinches, Searing Tool)
Advanced Engineering Tools (Hand Drill, Jaws of Life / Syndicate Jaws of Life)
Combat Wrench
Cleaving Saw
Telescopic Batons / Contractor Batons
Roasting Stick
Telescopic Riot Shield
Energy Shield / Bananium Energy Shield
This PR also touches up the code around the various above items.
679 lines
25 KiB
Plaintext
679 lines
25 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.
|
||
|
||
///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()
|
||
. = ..()
|
||
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)
|
||
|
||
/**
|
||
*
|
||
* 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/process_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE)
|
||
if(!semi_auto && from_firing)
|
||
return
|
||
var/obj/item/ammo_casing/AC = chambered //Find chambered round
|
||
if(istype(AC)) //there's a chambered round
|
||
if(casing_ejector || !from_firing)
|
||
AC.forceMove(drop_location()) //Eject casing onto ground.
|
||
AC.bounce_away(TRUE)
|
||
SEND_SIGNAL(AC, 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()
|
||
|
||
/obj/item/gun/ballistic/can_shoot()
|
||
return chambered
|
||
|
||
/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
|
||
|
||
/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()
|
||
|
||
///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)
|
||
. += "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/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 syndicate small-arms suppressor for maximum espionage."
|
||
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."
|