mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-28 01:51:46 +00:00
* Make energy guns able to use burst fire. * Changed obj/item/weapon/gun/projectile to /gun/ballistic and the name of the folder from "projectile" to "ballistic" to avoid confusion between actually projectiles and guns. Syringe gun, energy guns and magic guns can now use burst fire. * fixing merge conflict shit * fixing map conflicts * more map conflict fix * two tiny fixes. * tiny tweak * fixing merge conflicts. Moving the practice mini egun to the gun module. Renamed nuclear.dm to energy_gun.dm * map conflict fixes
487 lines
14 KiB
Plaintext
487 lines
14 KiB
Plaintext
/obj/item/weapon/gun
|
|
name = "gun"
|
|
desc = "It's a gun. It's pretty terrible, though."
|
|
icon = 'icons/obj/guns/projectile.dmi'
|
|
icon_state = "detective"
|
|
item_state = "gun"
|
|
flags = CONDUCT
|
|
slot_flags = SLOT_BELT
|
|
materials = list(MAT_METAL=2000)
|
|
w_class = 3
|
|
throwforce = 5
|
|
throw_speed = 3
|
|
throw_range = 5
|
|
force = 5
|
|
origin_tech = "combat=1"
|
|
needs_permit = 1
|
|
unique_rename = 0
|
|
attack_verb = list("struck", "hit", "bashed")
|
|
|
|
var/fire_sound = "gunshot"
|
|
var/suppressed = 0 //whether or not a message is displayed when fired
|
|
var/can_suppress = 0
|
|
var/can_unsuppress = 1
|
|
var/recoil = 0 //boom boom shake the room
|
|
var/clumsy_check = 1
|
|
var/obj/item/ammo_casing/chambered = null
|
|
var/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_state = SAWN_INTACT
|
|
var/burst_size = 1 //how large a burst is
|
|
var/fire_delay = 0 //rate of fire for burst firing and semi auto
|
|
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/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once.
|
|
var/unique_reskin = 0 //allows one-time reskinning
|
|
var/current_skin = null //the skin choice if we had a reskin
|
|
var/list/options = list()
|
|
|
|
lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/guns_righthand.dmi'
|
|
|
|
var/obj/item/device/firing_pin/pin = /obj/item/device/firing_pin //standard firing pin for most guns
|
|
|
|
var/obj/item/device/flashlight/gun_light = null
|
|
var/can_flashlight = 0
|
|
|
|
var/list/upgrades = list()
|
|
|
|
var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite
|
|
var/ammo_y_offset = 0
|
|
var/flight_x_offset = 0
|
|
var/flight_y_offset = 0
|
|
|
|
//Zooming
|
|
var/zoomable = FALSE //whether the gun generates a Zoom action on creation
|
|
var/zoomed = FALSE //Zoom toggle
|
|
var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect)
|
|
var/datum/action/toggle_scope_zoom/azoom
|
|
|
|
|
|
/obj/item/weapon/gun/New()
|
|
..()
|
|
if(pin)
|
|
pin = new pin(src)
|
|
if(gun_light)
|
|
verbs += /obj/item/weapon/gun/proc/toggle_gunlight
|
|
new /datum/action/item_action/toggle_gunlight(src)
|
|
build_zooming()
|
|
|
|
|
|
/obj/item/weapon/gun/CheckParts(list/parts_list)
|
|
..()
|
|
var/obj/item/weapon/gun/G = locate(/obj/item/weapon/gun) in contents
|
|
if(G)
|
|
G.loc = loc
|
|
qdel(G.pin)
|
|
G.pin = null
|
|
visible_message("[G] can now fit a new pin, but old one was destroyed in the process.", null, null, 3)
|
|
qdel(src)
|
|
|
|
/obj/item/weapon/gun/examine(mob/user)
|
|
..()
|
|
if(pin)
|
|
user << "It has [pin] installed."
|
|
else
|
|
user << "It doesn't have a firing pin installed, and won't fire."
|
|
if(unique_reskin && !current_skin)
|
|
user << "<span class='notice'>Alt-click it to reskin it.</span>"
|
|
|
|
//called after the gun has successfully fired its chambered ammo.
|
|
/obj/item/weapon/gun/proc/process_chamber()
|
|
return 0
|
|
|
|
|
|
//check if there's enough ammo/energy/whatever to shoot one time
|
|
//i.e if clicking would make it shoot
|
|
/obj/item/weapon/gun/proc/can_shoot()
|
|
return 1
|
|
|
|
|
|
/obj/item/weapon/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj)
|
|
user << "<span class='danger'>*click*</span>"
|
|
playsound(user, 'sound/weapons/empty.ogg', 100, 1)
|
|
|
|
|
|
/obj/item/weapon/gun/proc/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1)
|
|
if(recoil)
|
|
shake_camera(user, recoil + 1, recoil)
|
|
|
|
if(suppressed)
|
|
playsound(user, fire_sound, 10, 1)
|
|
else
|
|
playsound(user, fire_sound, 50, 1)
|
|
if(message)
|
|
if(pointblank)
|
|
user.visible_message("<span class='danger'>[user] fires [src] point blank at [pbtarget]!</span>", null, null, COMBAT_MESSAGE_RANGE)
|
|
else
|
|
user.visible_message("<span class='danger'>[user] fires [src]!</span>", null, null, COMBAT_MESSAGE_RANGE)
|
|
|
|
if(weapon_weight >= WEAPON_MEDIUM)
|
|
if(user.get_inactive_held_item())
|
|
if(prob(15))
|
|
if(user.drop_item())
|
|
user.visible_message("<span class='danger'>[src] flies out of [user]'s hands!</span>", "<span class='userdanger'>[src] kicks out of your grip!</span>")
|
|
|
|
/obj/item/weapon/gun/emp_act(severity)
|
|
for(var/obj/O in contents)
|
|
O.emp_act(severity)
|
|
|
|
|
|
/obj/item/weapon/gun/afterattack(atom/target, mob/living/user, flag, params)
|
|
if(firing_burst)
|
|
return
|
|
if(flag) //It's adjacent, is the user, or is on the user's person
|
|
if(target in user.contents) //can't shoot stuff inside us.
|
|
return
|
|
if(!ismob(target) || user.a_intent == "harm") //melee attack
|
|
return
|
|
if(target == user && user.zone_selected != "mouth") //so we can't shoot ourselves (unless mouth selected)
|
|
return
|
|
|
|
if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can.
|
|
var/mob/living/L = user
|
|
if(!can_trigger_gun(L))
|
|
return
|
|
|
|
if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can shoot.
|
|
shoot_with_empty_chamber(user)
|
|
return
|
|
|
|
if(flag)
|
|
if(user.zone_selected == "mouth")
|
|
handle_suicide(user, target, params)
|
|
return
|
|
|
|
|
|
//Exclude lasertag guns from the CLUMSY check.
|
|
if(clumsy_check)
|
|
if(istype(user))
|
|
if (user.disabilities & CLUMSY && prob(40))
|
|
user << "<span class='userdanger'>You shoot yourself in the foot with [src]!</span>"
|
|
var/shot_leg = pick("l_leg", "r_leg")
|
|
process_fire(user,user,0,params, zone_override = shot_leg)
|
|
user.drop_item()
|
|
return
|
|
|
|
if(weapon_weight == WEAPON_HEAVY && user.get_inactive_held_item())
|
|
user << "<span class='userdanger'>You need both hands free to fire [src]!</span>"
|
|
return
|
|
|
|
process_fire(target,user,1,params)
|
|
|
|
|
|
|
|
/obj/item/weapon/gun/proc/can_trigger_gun(var/mob/living/user)
|
|
|
|
if(!handle_pins(user) || !user.can_use_guns(src))
|
|
return 0
|
|
|
|
return 1
|
|
|
|
/obj/item/weapon/gun/proc/handle_pins(mob/living/user)
|
|
if(pin)
|
|
if(pin.pin_auth(user) || pin.emagged)
|
|
return 1
|
|
else
|
|
pin.auth_fail(user)
|
|
return 0
|
|
else
|
|
user << "<span class='warning'>[src]'s trigger is locked. This weapon doesn't have a firing pin installed!</span>"
|
|
return 0
|
|
|
|
obj/item/weapon/gun/proc/recharge_newshot()
|
|
return
|
|
|
|
/obj/item/weapon/gun/proc/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override)
|
|
add_fingerprint(user)
|
|
|
|
if(semicd)
|
|
return
|
|
|
|
if(weapon_weight)
|
|
if(user.get_inactive_held_item())
|
|
recoil = 4 //one-handed kick
|
|
else
|
|
recoil = initial(recoil)
|
|
|
|
if(burst_size > 1)
|
|
firing_burst = 1
|
|
for(var/i = 1 to burst_size)
|
|
if(!user)
|
|
break
|
|
if(!issilicon(user))
|
|
if( i>1 && !(user.is_holding(src))) //for burst firing
|
|
break
|
|
if(chambered && chambered.BB)
|
|
var/sprd = 0
|
|
if(randomspread)
|
|
sprd = round((rand() - 0.5) * spread)
|
|
else //Smart spread
|
|
sprd = round((i / burst_size - 0.5) * spread)
|
|
|
|
if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd))
|
|
shoot_with_empty_chamber(user)
|
|
break
|
|
else
|
|
if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot
|
|
shoot_live_shot(user, 1, target, message)
|
|
else
|
|
shoot_live_shot(user, 0, target, message)
|
|
else
|
|
shoot_with_empty_chamber(user)
|
|
break
|
|
process_chamber()
|
|
update_icon()
|
|
sleep(fire_delay)
|
|
firing_burst = 0
|
|
else
|
|
if(chambered)
|
|
if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, spread))
|
|
shoot_with_empty_chamber(user)
|
|
return
|
|
else
|
|
if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot
|
|
shoot_live_shot(user, 1, target, message)
|
|
else
|
|
shoot_live_shot(user, 0, target, message)
|
|
else
|
|
shoot_with_empty_chamber(user)
|
|
return
|
|
process_chamber()
|
|
update_icon()
|
|
semicd = 1
|
|
spawn(fire_delay)
|
|
semicd = 0
|
|
|
|
if(user)
|
|
user.update_inv_hands()
|
|
feedback_add_details("gun_fired","[src.type]")
|
|
|
|
/obj/item/weapon/gun/attack(mob/M as mob, mob/user)
|
|
if(user.a_intent == "harm") //Flogging
|
|
..()
|
|
else
|
|
return
|
|
|
|
/obj/item/weapon/gun/attackby(obj/item/I, mob/user, params)
|
|
if(can_flashlight)
|
|
if(istype(I, /obj/item/device/flashlight/seclite))
|
|
var/obj/item/device/flashlight/seclite/S = I
|
|
if(!gun_light)
|
|
if(!user.unEquip(I))
|
|
return
|
|
user << "<span class='notice'>You click [S] into place on [src].</span>"
|
|
if(S.on)
|
|
SetLuminosity(0)
|
|
gun_light = S
|
|
I.loc = src
|
|
update_icon()
|
|
update_gunlight(user)
|
|
verbs += /obj/item/weapon/gun/proc/toggle_gunlight
|
|
var/datum/action/A = new /datum/action/item_action/toggle_gunlight(src)
|
|
if(loc == user)
|
|
A.Grant(user)
|
|
|
|
if(istype(I, /obj/item/weapon/screwdriver))
|
|
if(gun_light)
|
|
for(var/obj/item/device/flashlight/seclite/S in src)
|
|
user << "<span class='notice'>You unscrew the seclite from [src].</span>"
|
|
gun_light = null
|
|
S.forceMove(get_turf(user))
|
|
update_gunlight(user)
|
|
S.update_brightness(user)
|
|
update_icon()
|
|
verbs -= /obj/item/weapon/gun/proc/toggle_gunlight
|
|
for(var/datum/action/item_action/toggle_gunlight/TGL in actions)
|
|
qdel(TGL)
|
|
|
|
|
|
|
|
/obj/item/weapon/gun/proc/toggle_gunlight()
|
|
set name = "Toggle Gunlight"
|
|
set category = "Object"
|
|
set desc = "Click to toggle your weapon's attached flashlight."
|
|
|
|
if(!gun_light)
|
|
return
|
|
|
|
var/mob/living/carbon/human/user = usr
|
|
gun_light.on = !gun_light.on
|
|
user << "<span class='notice'>You toggle the gunlight [gun_light.on ? "on":"off"].</span>"
|
|
|
|
playsound(user, 'sound/weapons/empty.ogg', 100, 1)
|
|
update_gunlight(user)
|
|
return
|
|
|
|
/obj/item/weapon/gun/proc/update_gunlight(mob/user = null)
|
|
if(gun_light)
|
|
if(gun_light.on)
|
|
if(loc == user)
|
|
user.AddLuminosity(gun_light.brightness_on)
|
|
else if(isturf(loc))
|
|
SetLuminosity(gun_light.brightness_on)
|
|
else
|
|
if(loc == user)
|
|
user.AddLuminosity(-gun_light.brightness_on)
|
|
else if(isturf(loc))
|
|
SetLuminosity(0)
|
|
update_icon()
|
|
else
|
|
if(loc == user)
|
|
user.AddLuminosity(-5)
|
|
else if(isturf(loc))
|
|
SetLuminosity(0)
|
|
for(var/X in actions)
|
|
var/datum/action/A = X
|
|
A.UpdateButtonIcon()
|
|
|
|
|
|
/obj/item/weapon/gun/pickup(mob/user)
|
|
..()
|
|
if(gun_light)
|
|
if(gun_light.on)
|
|
user.AddLuminosity(gun_light.brightness_on)
|
|
SetLuminosity(0)
|
|
if(azoom)
|
|
azoom.Grant(user)
|
|
|
|
/obj/item/weapon/gun/dropped(mob/user)
|
|
..()
|
|
if(gun_light)
|
|
if(gun_light.on)
|
|
user.AddLuminosity(-gun_light.brightness_on)
|
|
SetLuminosity(gun_light.brightness_on)
|
|
zoom(user,FALSE)
|
|
if(azoom)
|
|
azoom.Remove(user)
|
|
|
|
|
|
/obj/item/weapon/gun/AltClick(mob/user)
|
|
..()
|
|
if(user.incapacitated())
|
|
user << "<span class='warning'>You can't do that right now!</span>"
|
|
return
|
|
if(unique_reskin && !current_skin && loc == user)
|
|
reskin_gun(user)
|
|
|
|
|
|
/obj/item/weapon/gun/proc/reskin_gun(mob/M)
|
|
var/choice = input(M,"Warning, you can only reskin your weapon once!","Reskin Gun") in options
|
|
|
|
if(src && choice && !current_skin && !M.incapacitated() && in_range(M,src))
|
|
if(options[choice] == null)
|
|
return
|
|
current_skin = options[choice]
|
|
M << "Your gun is now skinned as [choice]. Say hello to your new friend."
|
|
update_icon()
|
|
|
|
|
|
|
|
|
|
/obj/item/weapon/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params)
|
|
if(!ishuman(user) || !ishuman(target))
|
|
return
|
|
|
|
if(semicd)
|
|
return
|
|
|
|
if(user == target)
|
|
target.visible_message("<span class='warning'>[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...</span>", \
|
|
"<span class='userdanger'>You stick [src] in your mouth, ready to pull the trigger...</span>")
|
|
else
|
|
target.visible_message("<span class='warning'>[user] points [src] at [target]'s head, ready to pull the trigger...</span>", \
|
|
"<span class='userdanger'>[user] points [src] at your head, ready to pull the trigger...</span>")
|
|
|
|
semicd = 1
|
|
|
|
if(!do_mob(user, target, 120) || user.zone_selected != "mouth")
|
|
if(user)
|
|
if(user == target)
|
|
user.visible_message("<span class='notice'>[user] decided life was worth living.</span>")
|
|
else if(target && target.Adjacent(user))
|
|
target.visible_message("<span class='notice'>[user] has decided to spare [target]'s life.</span>", "<span class='notice'>[user] has decided to spare your life!</span>")
|
|
semicd = 0
|
|
return
|
|
|
|
semicd = 0
|
|
|
|
target.visible_message("<span class='warning'>[user] pulls the trigger!</span>", "<span class='userdanger'>[user] pulls the trigger!</span>")
|
|
|
|
if(chambered && chambered.BB)
|
|
chambered.BB.damage *= 5
|
|
|
|
process_fire(target, user, 1, params)
|
|
|
|
/obj/item/weapon/gun/proc/unlock() //used in summon guns and as a convience for admins
|
|
if(pin)
|
|
qdel(pin)
|
|
pin = new /obj/item/device/firing_pin
|
|
|
|
/////////////
|
|
// ZOOMING //
|
|
/////////////
|
|
|
|
/datum/action/toggle_scope_zoom
|
|
name = "Toggle Scope"
|
|
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING
|
|
button_icon_state = "sniper_zoom"
|
|
var/obj/item/weapon/gun/gun = null
|
|
|
|
/datum/action/toggle_scope_zoom/Trigger()
|
|
gun.zoom(owner)
|
|
|
|
/datum/action/toggle_scope_zoom/IsAvailable()
|
|
. = ..()
|
|
if(!. && gun)
|
|
gun.zoom(owner, FALSE)
|
|
|
|
/datum/action/toggle_scope_zoom/Remove(mob/living/L)
|
|
gun.zoom(L, FALSE)
|
|
..()
|
|
|
|
|
|
/obj/item/weapon/gun/proc/zoom(mob/living/user, forced_zoom)
|
|
if(!user || !user.client)
|
|
return
|
|
|
|
switch(forced_zoom)
|
|
if(FALSE)
|
|
zoomed = FALSE
|
|
if(TRUE)
|
|
zoomed = TRUE
|
|
else
|
|
zoomed = !zoomed
|
|
|
|
if(zoomed)
|
|
var/_x = 0
|
|
var/_y = 0
|
|
switch(user.dir)
|
|
if(NORTH)
|
|
_y = zoom_amt
|
|
if(EAST)
|
|
_x = zoom_amt
|
|
if(SOUTH)
|
|
_y = -zoom_amt
|
|
if(WEST)
|
|
_x = -zoom_amt
|
|
|
|
user.client.pixel_x = world.icon_size*_x
|
|
user.client.pixel_y = world.icon_size*_y
|
|
else
|
|
user.client.pixel_x = 0
|
|
user.client.pixel_y = 0
|
|
|
|
|
|
//Proc, so that gun accessories/scopes/etc. can easily add zooming.
|
|
/obj/item/weapon/gun/proc/build_zooming()
|
|
if(azoom)
|
|
return
|
|
|
|
if(zoomable)
|
|
azoom = new()
|
|
azoom.gun = src
|
|
|