mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
727 lines
25 KiB
Plaintext
727 lines
25 KiB
Plaintext
/*
|
|
Defines a firing mode for a gun.
|
|
|
|
A firemode is created from a list of fire mode settings. Each setting modifies the value of the gun var with the same name.
|
|
If the fire mode value for a setting is null, it will be replaced with the initial value of that gun's variable when the firemode is created.
|
|
Obviously not compatible with variables that take a null value. If a setting is not present, then the corresponding var will not be modified.
|
|
*/
|
|
/datum/firemode
|
|
var/name = "default"
|
|
var/list/settings = list()
|
|
|
|
/datum/firemode/New(obj/item/weapon/gun/gun, list/properties = null)
|
|
..()
|
|
if(!properties) return
|
|
|
|
for(var/propname in properties)
|
|
var/propvalue = properties[propname]
|
|
|
|
if(propname == "mode_name")
|
|
name = propvalue
|
|
if(isnull(propvalue))
|
|
settings[propname] = gun.vars[propname] //better than initial() as it handles list vars like burst_accuracy
|
|
else
|
|
settings[propname] = propvalue
|
|
|
|
/datum/firemode/proc/apply_to(obj/item/weapon/gun/gun)
|
|
for(var/propname in settings)
|
|
gun.vars[propname] = settings[propname]
|
|
|
|
//Parent gun type. Guns are weapons that can be aimed at mobs and act over a distance
|
|
/obj/item/weapon/gun
|
|
name = "gun"
|
|
desc = "Its a gun. It's pretty terrible, though."
|
|
icon = 'icons/obj/gun.dmi'
|
|
item_icons = list(
|
|
slot_l_hand_str = 'icons/mob/items/lefthand_guns.dmi',
|
|
slot_r_hand_str = 'icons/mob/items/righthand_guns.dmi',
|
|
)
|
|
icon_state = "detective"
|
|
item_state = "gun"
|
|
flags = CONDUCT
|
|
slot_flags = SLOT_BELT|SLOT_HOLSTER
|
|
matter = list(DEFAULT_WALL_MATERIAL = 2000)
|
|
w_class = ITEMSIZE_NORMAL
|
|
throwforce = 5
|
|
throw_speed = 4
|
|
throw_range = 5
|
|
force = 5
|
|
preserve_item = 1
|
|
origin_tech = list(TECH_COMBAT = 1)
|
|
attack_verb = list("struck", "hit", "bashed")
|
|
zoomdevicename = "scope"
|
|
|
|
var/automatic = 0
|
|
var/burst = 1
|
|
var/fire_delay = 6 //delay after shooting before the gun can be used again
|
|
var/burst_delay = 2 //delay between shots, if firing in bursts
|
|
var/move_delay = 1
|
|
var/fire_sound = 'sound/weapons/Gunshot.ogg'
|
|
var/fire_sound_text = "gunshot"
|
|
var/recoil = 0 //screen shake
|
|
var/silenced = 0
|
|
var/muzzle_flash = 3
|
|
var/accuracy = 0 //Accuracy is measured in percents. +15 accuracy means that everything is effectively one tile closer for the purpose of miss chance, -15 means the opposite. launchers are not supported, at the moment.
|
|
var/scoped_accuracy = null
|
|
var/list/burst_accuracy = list(0) //allows for different accuracies for each shot in a burst. Applied on top of accuracy
|
|
var/list/dispersion = list(0)
|
|
var/mode_name = null
|
|
var/projectile_type = /obj/item/projectile //On ballistics, only used to check for the cham gun
|
|
|
|
var/wielded_item_state
|
|
var/one_handed_penalty = 0 // Penalty applied if someone fires a two-handed gun with one hand.
|
|
var/obj/screen/auto_target/auto_target
|
|
var/shooting = 0
|
|
var/next_fire_time = 0
|
|
|
|
var/sel_mode = 1 //index of the currently selected mode
|
|
var/list/firemodes = list()
|
|
|
|
//aiming system stuff
|
|
var/keep_aim = 1 //1 for keep shooting until aim is lowered
|
|
//0 for one bullet after tarrget moves and aim is lowered
|
|
var/multi_aim = 0 //Used to determine if you can target multiple people.
|
|
var/tmp/list/mob/living/aim_targets //List of who yer targeting.
|
|
var/tmp/mob/living/last_moved_mob //Used to fire faster at more than one person.
|
|
var/tmp/told_cant_shoot = 0 //So that it doesn't spam them with the fact they cannot hit them.
|
|
var/tmp/lock_time = -100
|
|
|
|
var/dna_lock = 0 //whether or not the gun is locked to dna
|
|
var/obj/item/dnalockingchip/attached_lock
|
|
|
|
var/last_shot = 0 //records the last shot fired
|
|
/obj/item/weapon/gun/New()
|
|
..()
|
|
for(var/i in 1 to firemodes.len)
|
|
firemodes[i] = new /datum/firemode(src, firemodes[i])
|
|
|
|
if(isnull(scoped_accuracy))
|
|
scoped_accuracy = accuracy
|
|
|
|
if(dna_lock)
|
|
attached_lock = new /obj/item/dnalockingchip(src)
|
|
if(!dna_lock)
|
|
verbs -= /obj/item/weapon/gun/verb/remove_dna
|
|
verbs -= /obj/item/weapon/gun/verb/give_dna
|
|
verbs -= /obj/item/weapon/gun/verb/allow_dna
|
|
|
|
/obj/item/weapon/gun/update_twohanding()
|
|
if(one_handed_penalty)
|
|
var/mob/living/M = loc
|
|
if(istype(M))
|
|
if(M.can_wield_item(src) && src.is_held_twohanded(M))
|
|
name = "[initial(name)] (wielded)"
|
|
else
|
|
name = initial(name)
|
|
else
|
|
name = initial(name)
|
|
update_icon() // In case item_state is set somewhere else.
|
|
..()
|
|
|
|
/obj/item/weapon/gun/update_held_icon()
|
|
if(wielded_item_state)
|
|
var/mob/living/M = loc
|
|
if(istype(M))
|
|
if(M.can_wield_item(src) && src.is_held_twohanded(M))
|
|
item_state_slots[slot_l_hand_str] = wielded_item_state
|
|
item_state_slots[slot_r_hand_str] = wielded_item_state
|
|
else
|
|
item_state_slots[slot_l_hand_str] = initial(item_state)
|
|
item_state_slots[slot_r_hand_str] = initial(item_state)
|
|
..()
|
|
|
|
|
|
//Checks whether a given mob can use the gun
|
|
//Any checks that shouldn't result in handle_click_empty() being called if they fail should go here.
|
|
//Otherwise, if you want handle_click_empty() to be called, check in consume_next_projectile() and return null there.
|
|
/obj/item/weapon/gun/proc/special_check(var/mob/user)
|
|
|
|
if(!istype(user, /mob/living))
|
|
return 0
|
|
if(!user.IsAdvancedToolUser())
|
|
return 0
|
|
|
|
var/mob/living/M = user
|
|
if(dna_lock && attached_lock.stored_dna)
|
|
if(!authorized_user(user))
|
|
if(attached_lock.safety_level == 0)
|
|
M << "<span class='danger'>\The [src] buzzes in dissapoint and displays an invalid DNA symbol.</span>"
|
|
return 0
|
|
if(!attached_lock.exploding)
|
|
if(attached_lock.safety_level == 1)
|
|
M << "<span class='danger'>\The [src] hisses in dissapointment.</span>"
|
|
visible_message("<span class='game say'><span class='name'>\The [src]</span> announces, \"Self-destruct occurring in ten seconds.\"</span>", "<span class='game say'><span class='name'>\The [src]</span> announces, \"Self-destruct occurring in ten seconds.\"</span>")
|
|
spawn(100)
|
|
explosion(src, 0, 0, 3, 4)
|
|
attached_lock.exploding = 1
|
|
sleep(1)
|
|
qdel(src)
|
|
return 0
|
|
if(HULK in M.mutations)
|
|
M << "<span class='danger'>Your fingers are much too large for the trigger guard!</span>"
|
|
return 0
|
|
if((CLUMSY in M.mutations) && prob(40)) //Clumsy handling
|
|
var/obj/P = consume_next_projectile()
|
|
if(P)
|
|
if(process_projectile(P, user, user, pick("l_foot", "r_foot")))
|
|
handle_post_fire(user, user)
|
|
var/datum/gender/TU = gender_datums[user.get_visible_gender()]
|
|
user.visible_message(
|
|
"<span class='danger'>\The [user] shoots [TU.himself] in the foot with \the [src]!</span>",
|
|
"<span class='danger'>You shoot yourself in the foot with \the [src]!</span>"
|
|
)
|
|
M.drop_item()
|
|
else
|
|
handle_click_empty(user)
|
|
return 0
|
|
return 1
|
|
|
|
/obj/item/weapon/gun/emp_act(severity)
|
|
for(var/obj/O in contents)
|
|
O.emp_act(severity)
|
|
|
|
/obj/item/weapon/gun/afterattack(atom/A, mob/living/user, adjacent, params)
|
|
if(adjacent) return //A is adjacent, is the user, or is on the user's person
|
|
|
|
if(!user.aiming)
|
|
user.aiming = new(user)
|
|
|
|
if(user && user.client && user.aiming && user.aiming.active && user.aiming.aiming_at != A)
|
|
PreFire(A,user,params) //They're using the new gun system, locate what they're aiming at.
|
|
return
|
|
|
|
if(user && user.a_intent == I_HELP && user.is_preference_enabled(/datum/client_preference/safefiring)) //regardless of what happens, refuse to shoot if help intent is on
|
|
user << "<span class='warning'>You refrain from firing your [src] as your intent is set to help.</span>"
|
|
return
|
|
|
|
else
|
|
Fire(A, user, params) //Otherwise, fire normally.
|
|
return
|
|
|
|
/* //Commented out for quality control and testing
|
|
if(automatic == 1)//Are we are going to be using automatic shooting
|
|
//We check to make sure they can fire
|
|
if(!special_check(user))
|
|
return
|
|
if(auto_target)//If they already have one then update it
|
|
auto_target.loc = get_turf(A)
|
|
auto_target.delay_del = 1//And reset the del so its like they got a new one and doesnt instantly vanish
|
|
to_chat(user, "<span class='notice'>You ready \the [src]! Click and drag the target around to shoot.</span>")
|
|
else//Otherwise just make a new one
|
|
auto_target = new/obj/screen/auto_target(get_turf(A), src)
|
|
visible_message("<span class='danger'>\[user] readies the [src]!</span>")
|
|
playsound(src, 'sound/weapons/TargetOn.ogg', 50, 1)
|
|
to_chat(user, "<span class='notice'>You ready \the [src]! Click and drag the target around to shoot.</span>")
|
|
return
|
|
Fire(A,user,params) //Otherwise, fire normally.
|
|
*/
|
|
|
|
/obj/item/weapon/gun/attack(atom/A, mob/living/user, def_zone)
|
|
if (A == user && user.zone_sel.selecting == O_MOUTH && !mouthshoot)
|
|
handle_suicide(user)
|
|
else if(user.a_intent == I_HURT) //point blank shooting
|
|
if(user && user.client && user.aiming && user.aiming.active && user.aiming.aiming_at != A && A != user)
|
|
PreFire(A,user) //They're using the new gun system, locate what they're aiming at.
|
|
return
|
|
else
|
|
Fire(A, user, pointblank=1)
|
|
else
|
|
return ..() //Pistolwhippin'
|
|
|
|
/obj/item/weapon/gun/attackby(var/obj/item/A as obj, mob/user as mob)
|
|
if(istype(A, /obj/item/dnalockingchip))
|
|
if(dna_lock)
|
|
user << "<span class='notice'>\The [src] already has a [attached_lock].</span>"
|
|
return
|
|
user << "<span class='notice'>You insert \the [A] into \the [src].</span>"
|
|
user.drop_item()
|
|
A.loc = src
|
|
attached_lock = A
|
|
dna_lock = 1
|
|
verbs += /obj/item/weapon/gun/verb/remove_dna
|
|
verbs += /obj/item/weapon/gun/verb/give_dna
|
|
verbs += /obj/item/weapon/gun/verb/allow_dna
|
|
return
|
|
|
|
if(istype(A, /obj/item/weapon/screwdriver))
|
|
if(dna_lock && attached_lock && !attached_lock.controller_lock)
|
|
user << "<span class='notice'>You begin removing \the [attached_lock] from \the [src].</span>"
|
|
playsound(src, A.usesound, 50, 1)
|
|
if(do_after(user, 25 * A.toolspeed))
|
|
user << "<span class='notice'>You remove \the [attached_lock] from \the [src].</span>"
|
|
user.put_in_hands(attached_lock)
|
|
dna_lock = 0
|
|
attached_lock = null
|
|
verbs -= /obj/item/weapon/gun/verb/remove_dna
|
|
verbs -= /obj/item/weapon/gun/verb/give_dna
|
|
verbs -= /obj/item/weapon/gun/verb/allow_dna
|
|
else
|
|
user << "<span class='warning'>\The [src] is not accepting modifications at this time.</span>"
|
|
..()
|
|
|
|
/obj/item/weapon/gun/emag_act(var/remaining_charges, var/mob/user)
|
|
if(dna_lock && attached_lock.controller_lock)
|
|
user << "<span class='notice'>You short circuit the internal locking mechanisms of \the [src]!</span>"
|
|
attached_lock.controller_dna = null
|
|
attached_lock.controller_lock = 0
|
|
attached_lock.stored_dna = list()
|
|
return 1
|
|
|
|
/obj/item/weapon/gun/MouseDrop(obj/over_object as obj)
|
|
if(!canremove)
|
|
return
|
|
|
|
if (ishuman(usr) || issmall(usr)) //so monkeys can take off their backpacks -- Urist
|
|
|
|
if (istype(usr.loc,/obj/mecha)) // stops inventory actions in a mech. why?
|
|
return
|
|
|
|
if (!( istype(over_object, /obj/screen) ))
|
|
return ..()
|
|
|
|
//makes sure that the thing is equipped, so that we can't drag it into our hand from miles away.
|
|
//there's got to be a better way of doing this.
|
|
if (!(src.loc == usr) || (src.loc && src.loc.loc == usr))
|
|
return
|
|
|
|
if (( usr.restrained() ) || ( usr.stat ))
|
|
return
|
|
|
|
if ((src.loc == usr) && !(istype(over_object, /obj/screen)) && !usr.unEquip(src))
|
|
return
|
|
|
|
switch(over_object.name)
|
|
if("r_hand")
|
|
usr.u_equip(src)
|
|
usr.put_in_r_hand(src)
|
|
if("l_hand")
|
|
usr.u_equip(src)
|
|
usr.put_in_l_hand(src)
|
|
src.add_fingerprint(usr)
|
|
|
|
/obj/item/weapon/gun/proc/Fire(atom/target, mob/living/user, clickparams, pointblank=0, reflex=0)
|
|
if(!user || !target) return
|
|
if(target.z != user.z) return
|
|
|
|
add_fingerprint(user)
|
|
|
|
user.break_cloak()
|
|
|
|
if(!special_check(user))
|
|
return
|
|
|
|
if(world.time < next_fire_time)
|
|
if (world.time % 3) //to prevent spam
|
|
user << "<span class='warning'>[src] is not ready to fire again!</span>"
|
|
return
|
|
|
|
var/shoot_time = (burst - 1)* burst_delay
|
|
|
|
//These should apparently be disabled to allow for the automatic system to function without causing near-permanant paralysis. Re-enabling them while we sort that out.
|
|
user.setClickCooldown(shoot_time) //no clicking on things while shooting
|
|
user.setMoveCooldown(shoot_time) //no moving while shooting either
|
|
|
|
next_fire_time = world.time + shoot_time
|
|
|
|
var/held_twohanded = (user.can_wield_item(src) && src.is_held_twohanded(user))
|
|
|
|
//actually attempt to shoot
|
|
var/turf/targloc = get_turf(target) //cache this in case target gets deleted during shooting, e.g. if it was a securitron that got destroyed.
|
|
|
|
/* // Commented out for quality control and testing.
|
|
shooting = 1
|
|
if(automatic == 1 && auto_target && auto_target.active)//When we are going to shoot and have an auto_target AND its active meaning we clicked on it we tell it to burstfire 1000 rounds
|
|
burst = 1000//Yes its not EXACTLY full auto but when are we shooting more than 1000 normally and it can easily be made higher
|
|
*/
|
|
for(var/i in 1 to burst)
|
|
/* // Commented out for quality control and testing.
|
|
if(!reflex && automatic)//If we are shooting automatic then check our target, however if we are shooting reflex we dont use automatic
|
|
//extra sanity checking.
|
|
if(user.incapacitated())
|
|
return
|
|
if(user.get_active_hand() != src)
|
|
break
|
|
if(!auto_target) break//Stopped shooting
|
|
else if(auto_target.loc)
|
|
target = auto_target.loc
|
|
//Lastly just update our dir if needed
|
|
if(user.dir != get_dir(user, auto_target))
|
|
user.face_atom(auto_target)
|
|
*/
|
|
var/obj/projectile = consume_next_projectile(user)
|
|
if(!projectile)
|
|
handle_click_empty(user)
|
|
break
|
|
|
|
process_accuracy(projectile, user, target, i, held_twohanded)
|
|
|
|
if(pointblank)
|
|
process_point_blank(projectile, user, target)
|
|
|
|
if(process_projectile(projectile, user, target, user.zone_sel.selecting, clickparams))
|
|
handle_post_fire(user, target, pointblank, reflex)
|
|
update_icon()
|
|
|
|
if(i < burst)
|
|
sleep(burst_delay)
|
|
|
|
if(!(target && target.loc))
|
|
target = targloc
|
|
pointblank = 0
|
|
|
|
last_shot = world.time
|
|
|
|
/* // Commented out for quality control and testing.
|
|
shooting = 0
|
|
*/
|
|
// We do this down here, so we don't get the message if we fire an empty gun.
|
|
if(user.item_is_in_hands(src) && user.hands_are_full())
|
|
if(one_handed_penalty >= 20)
|
|
to_chat(user, "<span class='warning'>You struggle to keep \the [src] pointed at the correct position with just one hand!</span>")
|
|
|
|
var/target_for_log
|
|
if(ismob(target))
|
|
target_for_log = target
|
|
else
|
|
target_for_log = "[target.name]"
|
|
|
|
add_attack_logs(user,target_for_log,"Fired gun [src.name] ([reflex ? "REFLEX" : "MANUAL"])")
|
|
|
|
//update timing
|
|
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
|
|
user.setMoveCooldown(move_delay)
|
|
next_fire_time = world.time + fire_delay
|
|
|
|
accuracy = initial(accuracy) //Reset the gun's accuracy
|
|
|
|
if(muzzle_flash)
|
|
set_light(0)
|
|
|
|
// Similar to the above proc, but does not require a user, which is ideal for things like turrets.
|
|
/obj/item/weapon/gun/proc/Fire_userless(atom/target)
|
|
if(!target)
|
|
return
|
|
|
|
if(world.time < next_fire_time)
|
|
return
|
|
|
|
var/shoot_time = (burst - 1)* burst_delay
|
|
next_fire_time = world.time + shoot_time
|
|
|
|
var/turf/targloc = get_turf(target) //cache this in case target gets deleted during shooting, e.g. if it was a securitron that got destroyed.
|
|
for(var/i in 1 to burst)
|
|
var/obj/projectile = consume_next_projectile()
|
|
if(!projectile)
|
|
handle_click_empty()
|
|
break
|
|
|
|
if(istype(projectile, /obj/item/projectile))
|
|
var/obj/item/projectile/P = projectile
|
|
|
|
var/acc = burst_accuracy[min(i, burst_accuracy.len)]
|
|
var/disp = dispersion[min(i, dispersion.len)]
|
|
|
|
P.accuracy = accuracy + acc
|
|
P.dispersion = disp
|
|
|
|
P.shot_from = src.name
|
|
P.silenced = silenced
|
|
|
|
P.launch(target)
|
|
|
|
last_shot = world.time
|
|
|
|
if(silenced)
|
|
playsound(src, fire_sound, 10, 1)
|
|
else
|
|
playsound(src, fire_sound, 50, 1)
|
|
|
|
if(muzzle_flash)
|
|
set_light(muzzle_flash)
|
|
update_icon()
|
|
|
|
//process_accuracy(projectile, user, target, acc, disp)
|
|
|
|
// if(pointblank)
|
|
// process_point_blank(projectile, user, target)
|
|
|
|
// if(process_projectile(projectile, null, target, user.zone_sel.selecting, clickparams))
|
|
// handle_post_fire(null, target, pointblank, reflex)
|
|
|
|
// update_icon()
|
|
|
|
if(i < burst)
|
|
sleep(burst_delay)
|
|
|
|
if(!(target && target.loc))
|
|
target = targloc
|
|
//pointblank = 0
|
|
|
|
var/target_for_log
|
|
if(ismob(target))
|
|
target_for_log = target
|
|
else
|
|
target_for_log = "[target.name]"
|
|
|
|
add_attack_logs("Unmanned",target_for_log,"Fired [src.name]")
|
|
|
|
//update timing
|
|
next_fire_time = world.time + fire_delay
|
|
|
|
accuracy = initial(accuracy) //Reset the gun's accuracy
|
|
|
|
if(muzzle_flash)
|
|
set_light(0)
|
|
|
|
|
|
|
|
//obtains the next projectile to fire
|
|
/obj/item/weapon/gun/proc/consume_next_projectile()
|
|
return null
|
|
|
|
//used by aiming code
|
|
/obj/item/weapon/gun/proc/can_hit(atom/target as mob, var/mob/living/user as mob)
|
|
if(!special_check(user))
|
|
return 2
|
|
//just assume we can shoot through glass and stuff. No big deal, the player can just choose to not target someone
|
|
//on the other side of a window if it makes a difference. Or if they run behind a window, too bad.
|
|
return check_trajectory(target, user)
|
|
|
|
//called if there was no projectile to shoot
|
|
/obj/item/weapon/gun/proc/handle_click_empty(mob/user)
|
|
if (user)
|
|
user.visible_message("*click click*", "<span class='danger'>*click*</span>")
|
|
else
|
|
src.visible_message("*click click*")
|
|
playsound(src.loc, 'sound/weapons/empty.ogg', 100, 1)
|
|
|
|
//called after successfully firing
|
|
/obj/item/weapon/gun/proc/handle_post_fire(mob/user, atom/target, var/pointblank=0, var/reflex=0)
|
|
if(silenced)
|
|
if(reflex)
|
|
user.visible_message(
|
|
"<span class='reflex_shoot'><b>\The [user] fires \the [src][pointblank ? " point blank at \the [target]":""] by reflex!</b></span>",
|
|
"<span class='reflex_shoot'>You fire \the [src] by reflex!</span>",
|
|
"You hear a [fire_sound_text]!"
|
|
)
|
|
else
|
|
user.visible_message(
|
|
"<span class='danger'>\The [user] fires \the [src][pointblank ? " point blank at \the [target]":""]!</span>",
|
|
"<span class='warning'>You fire \the [src]!</span>",
|
|
"You hear a [fire_sound_text]!"
|
|
)
|
|
|
|
if(muzzle_flash)
|
|
set_light(muzzle_flash)
|
|
|
|
if(one_handed_penalty)
|
|
if(!src.is_held_twohanded(user))
|
|
switch(one_handed_penalty)
|
|
if(1 to 15)
|
|
if(prob(50)) //don't need to tell them every single time
|
|
to_chat(user, "<span class='warning'>Your aim wavers slightly.</span>")
|
|
if(16 to 30)
|
|
to_chat(user, "<span class='warning'>Your aim wavers as you fire \the [src] with just one hand.</span>")
|
|
if(31 to 45)
|
|
to_chat(user, "<span class='warning'>You have trouble keeping \the [src] on target with just one hand.</span>")
|
|
if(46 to INFINITY)
|
|
to_chat(user, "<span class='warning'>You struggle to keep \the [src] on target with just one hand!</span>")
|
|
else if(!user.can_wield_item(src))
|
|
switch(one_handed_penalty)
|
|
if(1 to 15)
|
|
if(prob(50)) //don't need to tell them every single time
|
|
to_chat(user, "<span class='warning'>Your aim wavers slightly.</span>")
|
|
if(16 to 30)
|
|
to_chat(user, "<span class='warning'>Your aim wavers as you try to hold \the [src] steady.</span>")
|
|
if(31 to 45)
|
|
to_chat(user, "<span class='warning'>You have trouble holding \the [src] steady.</span>")
|
|
if(46 to INFINITY)
|
|
to_chat(user, "<span class='warning'>You struggle to hold \the [src] steady!</span>")
|
|
|
|
if(recoil)
|
|
spawn()
|
|
shake_camera(user, recoil+1, recoil)
|
|
update_icon()
|
|
|
|
|
|
/obj/item/weapon/gun/proc/process_point_blank(obj/projectile, mob/user, atom/target)
|
|
var/obj/item/projectile/P = projectile
|
|
if(!istype(P))
|
|
return //default behaviour only applies to true projectiles
|
|
|
|
//default point blank multiplier
|
|
var/damage_mult = 1.3
|
|
|
|
//determine multiplier due to the target being grabbed
|
|
if(ismob(target))
|
|
var/mob/M = target
|
|
if(M.grabbed_by.len)
|
|
var/grabstate = 0
|
|
for(var/obj/item/weapon/grab/G in M.grabbed_by)
|
|
grabstate = max(grabstate, G.state)
|
|
if(grabstate >= GRAB_NECK)
|
|
damage_mult = 2.5
|
|
else if(grabstate >= GRAB_AGGRESSIVE)
|
|
damage_mult = 1.5
|
|
P.damage *= damage_mult
|
|
|
|
/obj/item/weapon/gun/proc/process_accuracy(obj/projectile, mob/living/user, atom/target, var/burst, var/held_twohanded)
|
|
var/obj/item/projectile/P = projectile
|
|
if(!istype(P))
|
|
return //default behaviour only applies to true projectiles
|
|
|
|
var/acc_mod = burst_accuracy[min(burst, burst_accuracy.len)]
|
|
var/disp_mod = dispersion[min(burst, dispersion.len)]
|
|
|
|
if(one_handed_penalty)
|
|
if(!held_twohanded)
|
|
acc_mod += -ceil(one_handed_penalty/2)
|
|
disp_mod += one_handed_penalty*0.5 //dispersion per point of two-handedness
|
|
|
|
//Accuracy modifiers
|
|
P.accuracy = accuracy + acc_mod
|
|
P.dispersion = disp_mod
|
|
|
|
// Certain statuses make it harder to aim, blindness especially. Same chances as melee, however guns accuracy uses multiples of 15.
|
|
if(user.eye_blind)
|
|
P.accuracy -= 75
|
|
if(user.eye_blurry)
|
|
P.accuracy -= 30
|
|
if(user.confused)
|
|
P.accuracy -= 45
|
|
|
|
//accuracy bonus from aiming
|
|
if (aim_targets && (target in aim_targets))
|
|
//If you aim at someone beforehead, it'll hit more often.
|
|
//Kinda balanced by fact you need like 2 seconds to aim
|
|
//As opposed to no-delay pew pew
|
|
P.accuracy += 30
|
|
|
|
// Some modifiers make it harder or easier to hit things.
|
|
for(var/datum/modifier/M in user.modifiers)
|
|
if(!isnull(M.accuracy))
|
|
P.accuracy += M.accuracy
|
|
if(!isnull(M.accuracy_dispersion))
|
|
P.dispersion = max(P.dispersion + M.accuracy_dispersion, 0)
|
|
|
|
//does the actual launching of the projectile
|
|
/obj/item/weapon/gun/proc/process_projectile(obj/projectile, mob/user, atom/target, var/target_zone, var/params=null)
|
|
var/obj/item/projectile/P = projectile
|
|
if(!istype(P))
|
|
return 0 //default behaviour only applies to true projectiles
|
|
|
|
if(params)
|
|
P.set_clickpoint(params)
|
|
|
|
//shooting while in shock
|
|
var/x_offset = 0
|
|
var/y_offset = 0
|
|
if(istype(user, /mob/living/carbon))
|
|
var/mob/living/carbon/mob = user
|
|
if(mob.shock_stage > 120)
|
|
y_offset = rand(-2,2)
|
|
x_offset = rand(-2,2)
|
|
else if(mob.shock_stage > 70)
|
|
y_offset = rand(-1,1)
|
|
x_offset = rand(-1,1)
|
|
|
|
var/launched = !P.launch_from_gun(target, user, src, target_zone, x_offset, y_offset)
|
|
|
|
if(launched)
|
|
play_fire_sound(user, P)
|
|
|
|
return launched
|
|
|
|
|
|
/obj/item/weapon/gun/proc/play_fire_sound(var/mob/user, var/obj/item/projectile/P)
|
|
var/shot_sound = (istype(P) && P.fire_sound)? P.fire_sound : fire_sound
|
|
if(silenced)
|
|
playsound(user, shot_sound, 10, 1)
|
|
else
|
|
playsound(user, shot_sound, 50, 1)
|
|
|
|
//Suicide handling.
|
|
/obj/item/weapon/gun/var/mouthshoot = 0 //To stop people from suiciding twice... >.>
|
|
|
|
/obj/item/weapon/gun/proc/handle_suicide(mob/living/user)
|
|
if(!ishuman(user))
|
|
return
|
|
var/mob/living/carbon/human/M = user
|
|
|
|
mouthshoot = 1
|
|
M.visible_message("<font color='red'>[user] sticks their gun in their mouth, ready to pull the trigger...</font>")
|
|
if(!do_after(user, 40))
|
|
M.visible_message("<font color='blue'>[user] decided life was worth living</font>")
|
|
mouthshoot = 0
|
|
return
|
|
var/obj/item/projectile/in_chamber = consume_next_projectile()
|
|
if (istype(in_chamber))
|
|
user.visible_message("<span class = 'warning'>[user] pulls the trigger.</span>")
|
|
var/shot_sound = in_chamber.fire_sound? in_chamber.fire_sound : fire_sound
|
|
if(silenced)
|
|
playsound(user, shot_sound, 10, 1)
|
|
else
|
|
playsound(user, shot_sound, 50, 1)
|
|
if(istype(in_chamber, /obj/item/projectile/beam/lastertag))
|
|
user.show_message("<span class = 'warning'>You feel rather silly, trying to commit suicide with a toy.</span>")
|
|
mouthshoot = 0
|
|
return
|
|
|
|
in_chamber.on_hit(M)
|
|
if (in_chamber.damage_type != HALLOSS)
|
|
log_and_message_admins("[key_name(user)] commited suicide using \a [src]")
|
|
user.apply_damage(in_chamber.damage*2.5, in_chamber.damage_type, "head", used_weapon = "Point blank shot in the mouth with \a [in_chamber]", sharp=1)
|
|
user.death()
|
|
else
|
|
user << "<span class = 'notice'>Ow...</span>"
|
|
user.apply_effect(110,AGONY,0)
|
|
qdel(in_chamber)
|
|
mouthshoot = 0
|
|
return
|
|
else
|
|
handle_click_empty(user)
|
|
mouthshoot = 0
|
|
return
|
|
|
|
/obj/item/weapon/gun/proc/toggle_scope(var/zoom_amount=2.0)
|
|
//looking through a scope limits your periphereal vision
|
|
//still, increase the view size by a tiny amount so that sniping isn't too restricted to NSEW
|
|
var/zoom_offset = round(world.view * zoom_amount)
|
|
var/view_size = round(world.view + zoom_amount)
|
|
var/scoped_accuracy_mod = zoom_offset
|
|
|
|
zoom(zoom_offset, view_size)
|
|
if(zoom)
|
|
accuracy = scoped_accuracy + scoped_accuracy_mod
|
|
if(recoil)
|
|
recoil = round(recoil*zoom_amount+1) //recoil is worse when looking through a scope
|
|
|
|
//make sure accuracy and recoil are reset regardless of how the item is unzoomed.
|
|
/obj/item/weapon/gun/zoom()
|
|
..()
|
|
if(!zoom)
|
|
accuracy = initial(accuracy)
|
|
recoil = initial(recoil)
|
|
|
|
/obj/item/weapon/gun/examine(mob/user)
|
|
..()
|
|
if(firemodes.len > 1)
|
|
var/datum/firemode/current_mode = firemodes[sel_mode]
|
|
to_chat(user, "The fire selector is set to [current_mode.name].")
|
|
|
|
/obj/item/weapon/gun/proc/switch_firemodes(mob/user)
|
|
if(firemodes.len <= 1)
|
|
return null
|
|
|
|
sel_mode++
|
|
if(sel_mode > firemodes.len)
|
|
sel_mode = 1
|
|
var/datum/firemode/new_mode = firemodes[sel_mode]
|
|
new_mode.apply_to(src)
|
|
to_chat(user, "<span class='notice'>\The [src] is now set to [new_mode.name].</span>")
|
|
|
|
return new_mode
|
|
|
|
/obj/item/weapon/gun/attack_self(mob/user)
|
|
switch_firemodes(user)
|