mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-01-05 23:12:26 +00:00
Adds gun firemodes and burst firing
Adds support for different gun firemodes, and defines firemodes for various automatic weapons, as well as the double-barreled shotgun
This commit is contained in:
@@ -1,3 +1,32 @@
|
||||
/*
|
||||
Defines a firing mode for a gun.
|
||||
|
||||
burst number of shots to fire in a burst
|
||||
burst_delay tick delay between shots in a burst
|
||||
fire_delay tick delay after the last shot before the gun may be used again
|
||||
move_delay tick delay after the last shot before the player may move
|
||||
dispersion dispersion of each shot in the burst measured in tiles per 7 tiles angle ratio
|
||||
accuracy accuracy modifier applied to each shot in tiles. Applied on top of the weapon accuracy.
|
||||
*/
|
||||
/datum/firemode
|
||||
var/name = "default"
|
||||
var/burst = 1
|
||||
var/burst_delay = null
|
||||
var/fire_delay = null
|
||||
var/move_delay = 1
|
||||
var/list/accuracy = list(0)
|
||||
var/list/dispersion = list(0)
|
||||
|
||||
//using a list makes defining fire modes for new guns much nicer,
|
||||
//however we convert the lists to datums in part so that firemodes can be VVed if necessary.
|
||||
/datum/firemode/New(list/properties = null)
|
||||
..()
|
||||
if(!properties) return
|
||||
|
||||
for(var/propname in vars)
|
||||
if(!isnull(properties[propname]))
|
||||
src.vars[propname] = properties[propname]
|
||||
|
||||
//Parent gun type. Guns are weapons that can be aimed at mobs and act over a distance
|
||||
/obj/item/weapon/gun
|
||||
name = "gun"
|
||||
@@ -22,14 +51,18 @@
|
||||
zoomdevicename = "scope"
|
||||
|
||||
var/fire_delay = 6
|
||||
var/burst_delay = 2 //delay between shots, if firing in bursts
|
||||
var/fire_sound = 'sound/weapons/Gunshot.ogg'
|
||||
var/fire_sound_text = "gunshot"
|
||||
var/recoil = 0 //screen shake
|
||||
var/silenced = 0
|
||||
var/accuracy = 0 //accuracy is measured in tiles. +1 accuracy means that everything is effectively one tile closer for the purpose of miss chance, -1 means the opposite. launchers are not supported, at the moment.
|
||||
var/accuracy = 0 //accuracy is measured in tiles. +1 accuracy means that everything is effectively one tile closer for the purpose of miss chance, -1 means the opposite. launchers are not supported, at the moment.
|
||||
var/scoped_accuracy = null
|
||||
|
||||
var/last_fired = 0
|
||||
var/next_fire_time = 0
|
||||
|
||||
var/sel_mode = 1 //index of the currently selected mode
|
||||
var/list/firemodes = null
|
||||
|
||||
//aiming system stuff
|
||||
var/keep_aim = 1 //1 for keep shooting until aim is lowered
|
||||
@@ -42,17 +75,18 @@
|
||||
|
||||
/obj/item/weapon/gun/New()
|
||||
..()
|
||||
if(!firemodes || !firemodes.len)
|
||||
firemodes = list( new/datum/firemode )
|
||||
else
|
||||
for(var/i in 1 to firemodes.len)
|
||||
firemodes[i] = new/datum/firemode(firemodes[i])
|
||||
|
||||
if(firemodes.len <= 1)
|
||||
verbs -= /obj/item/weapon/gun/verb/switch_firemodes
|
||||
|
||||
if(isnull(scoped_accuracy))
|
||||
scoped_accuracy = accuracy
|
||||
|
||||
//Returns 1 if the gun is able to be fired
|
||||
/obj/item/weapon/gun/proc/ready_to_fire()
|
||||
if(world.time >= last_fired + fire_delay)
|
||||
last_fired = world.time
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
|
||||
//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.
|
||||
@@ -114,7 +148,7 @@
|
||||
else
|
||||
return ..() //Pistolwhippin'
|
||||
|
||||
/obj/item/weapon/gun/proc/Fire(atom/target, mob/living/user, params, pointblank=0, reflex=0)
|
||||
/obj/item/weapon/gun/proc/Fire(atom/target, mob/living/user, clickparams, pointblank=0, reflex=0)
|
||||
if(!user || !target) return
|
||||
|
||||
add_fingerprint(user)
|
||||
@@ -122,24 +156,49 @@
|
||||
if(!special_check(user))
|
||||
return
|
||||
|
||||
if (!ready_to_fire())
|
||||
if(world.time < next_fire_time)
|
||||
if (world.time % 3) //to prevent spam
|
||||
user << "<span class='warning'>[src] is not ready to fire again!"
|
||||
return
|
||||
|
||||
//unpack firemode data
|
||||
var/datum/firemode/firemode = firemodes[sel_mode]
|
||||
var/_burst = firemode.burst
|
||||
var/_burst_delay = isnull(firemode.burst_delay)? src.burst_delay : firemode.burst_delay
|
||||
var/_fire_delay = isnull(firemode.fire_delay)? src.fire_delay : firemode.fire_delay
|
||||
var/_move_delay = firemode.move_delay
|
||||
|
||||
var/obj/projectile = consume_next_projectile(user)
|
||||
if(!projectile)
|
||||
handle_click_empty(user)
|
||||
return
|
||||
var/shoot_time = (_burst - 1)*_burst_delay
|
||||
user.next_move = world.time + shoot_time //no clicking on things while shooting
|
||||
if(user.client) user.client.move_delay = world.time + shoot_time //no moving while shooting either
|
||||
next_fire_time = world.time + shoot_time
|
||||
|
||||
//actually attempt to shoot
|
||||
for(var/i in 1 to _burst)
|
||||
var/obj/projectile = consume_next_projectile(user)
|
||||
if(!projectile)
|
||||
handle_click_empty(user)
|
||||
break
|
||||
|
||||
var/acc = firemode.accuracy[min(i, firemode.accuracy.len)]
|
||||
var/disp = firemode.dispersion[min(i, firemode.dispersion.len)]
|
||||
process_accuracy(projectile, user, target, acc, disp)
|
||||
|
||||
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()
|
||||
|
||||
sleep(_burst_delay)
|
||||
|
||||
update_held_icon()
|
||||
|
||||
//update timing
|
||||
user.next_move = world.time + 4
|
||||
|
||||
if(process_projectile(projectile, user, target, user.zone_sel.selecting, params, pointblank, reflex))
|
||||
handle_post_fire(user, target, pointblank, reflex)
|
||||
|
||||
update_icon()
|
||||
update_held_icon()
|
||||
|
||||
if(user.client) user.client.move_delay = world.time + _move_delay
|
||||
next_fire_time = world.time + _fire_delay
|
||||
|
||||
//obtains the next projectile to fire
|
||||
/obj/item/weapon/gun/proc/consume_next_projectile()
|
||||
@@ -186,12 +245,52 @@
|
||||
shake_camera(user, recoil+1, recoil)
|
||||
update_icon()
|
||||
|
||||
//does the actual shooting
|
||||
/obj/item/weapon/gun/proc/process_projectile(obj/projectile, mob/user, atom/target, var/target_zone, var/params=null, var/pointblank=0, var/reflex=0)
|
||||
if(!istype(projectile, /obj/item/projectile))
|
||||
|
||||
/obj/item/weapon/gun/proc/process_point_blank(obj/projectile, mob/user, atom/target)
|
||||
var/obj/item/projectile/P = projectile
|
||||
if(!istype(projectile))
|
||||
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 = 3.0
|
||||
else if(grabstate >= GRAB_AGGRESSIVE)
|
||||
damage_mult = 1.5
|
||||
P.damage *= damage_mult
|
||||
|
||||
/obj/item/weapon/gun/proc/process_accuracy(obj/projectile, mob/user, atom/target, acc_mod, dispersion)
|
||||
var/obj/item/projectile/P = projectile
|
||||
if(!istype(projectile))
|
||||
return //default behaviour only applies to true projectiles
|
||||
|
||||
//Accuracy modifiers
|
||||
P.accuracy = accuracy + acc_mod
|
||||
P.dispersion = dispersion
|
||||
|
||||
//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 += 2
|
||||
|
||||
//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(projectile))
|
||||
return 0 //default behaviour only applies to true projectiles
|
||||
|
||||
var/obj/item/projectile/P = projectile
|
||||
if(params)
|
||||
P.set_clickpoint(params)
|
||||
|
||||
//shooting while in shock
|
||||
var/x_offset = 0
|
||||
@@ -205,27 +304,6 @@
|
||||
y_offset = rand(-1,1)
|
||||
x_offset = rand(-1,1)
|
||||
|
||||
//Point blank bonus
|
||||
if(pointblank)
|
||||
var/damage_mult = 1.3 //default point blank multiplier
|
||||
|
||||
//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 = 3.0
|
||||
else if (grabstate >= GRAB_AGGRESSIVE)
|
||||
damage_mult = 1.5
|
||||
|
||||
P.damage *= damage_mult
|
||||
|
||||
if(params)
|
||||
P.set_clickpoint(params)
|
||||
|
||||
return !P.launch(target, user, src, target_zone, x_offset, y_offset)
|
||||
|
||||
//Suicide handling.
|
||||
@@ -287,3 +365,22 @@
|
||||
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]
|
||||
user << "The fire selector is set to [current_mode.name]."
|
||||
|
||||
/obj/item/weapon/gun/verb/switch_firemodes()
|
||||
set name = "Switch Fire Mode"
|
||||
set category = "Object"
|
||||
set src in usr
|
||||
|
||||
if(usr.stat || usr.restrained()) return
|
||||
|
||||
sel_mode++
|
||||
if(sel_mode > firemodes.len)
|
||||
sel_mode = 1
|
||||
var/datum/firemode/new_mode = firemodes[sel_mode]
|
||||
usr << "<span class='notice'>You switch \the [src] to [new_mode.name].</span>"
|
||||
|
||||
@@ -189,9 +189,9 @@
|
||||
|
||||
/obj/item/weapon/gun/projectile/examine(mob/user)
|
||||
..(user)
|
||||
user << "Has [getAmmo()] round\s remaining."
|
||||
if(ammo_magazine)
|
||||
user << "It has \a [ammo_magazine] loaded."
|
||||
user << "Has [getAmmo()] round\s remaining."
|
||||
return
|
||||
|
||||
/obj/item/weapon/gun/projectile/proc/getAmmo()
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
slot_flags = SLOT_BELT
|
||||
ammo_type = /obj/item/ammo_casing/c9mm
|
||||
multi_aim = 1
|
||||
fire_delay = 0
|
||||
|
||||
firemodes = list(
|
||||
list(name="semiauto", burst=1, fire_delay=0),
|
||||
list(name="3-round bursts", burst=3, move_delay=4, accuracy = list(0,-1,-1,-2,-2), dispersion = list(0.0, 0.6, 1.0)),
|
||||
list(name="short bursts", burst=5, move_delay=4, accuracy = list(0,-1,-1,-2,-2), dispersion = list(0.6, 1.0, 1.0, 1.0, 1.2)),
|
||||
)
|
||||
|
||||
/obj/item/weapon/gun/projectile/automatic/mini_uzi
|
||||
name = "\improper Uzi"
|
||||
@@ -59,6 +64,12 @@
|
||||
slot_flags = SLOT_BACK
|
||||
load_method = MAGAZINE
|
||||
magazine_type = /obj/item/ammo_magazine/c762
|
||||
|
||||
firemodes = list(
|
||||
list(name="semiauto", burst=1, fire_delay=0),
|
||||
list(name="3-round bursts", burst=3, move_delay=6, accuracy = list(0,-1,-1,-2,-2), dispersion = list(0.0, 0.6, 0.6)),
|
||||
list(name="short bursts", burst=5, move_delay=6, accuracy = list(0,-1,-1,-2,-2), dispersion = list(0.6, 1.0, 1.0, 1.0, 1.2)),
|
||||
)
|
||||
|
||||
/obj/item/weapon/gun/projectile/automatic/sts35/update_icon()
|
||||
..()
|
||||
@@ -104,6 +115,12 @@
|
||||
auto_eject = 1
|
||||
auto_eject_sound = 'sound/weapons/smg_empty_alarm.ogg'
|
||||
|
||||
burst_delay = 4
|
||||
firemodes = list(
|
||||
list(name="semiauto", burst=1, fire_delay=0),
|
||||
list(name="3-round bursts", burst=3, move_delay=6, accuracy = list(0,-1,-1), dispersion = list(0.0, 0.6, 0.6)),
|
||||
)
|
||||
|
||||
var/use_launcher = 0
|
||||
var/obj/item/weapon/gun/launcher/grenade/underslung/launcher
|
||||
|
||||
@@ -166,6 +183,12 @@
|
||||
fire_sound = 'sound/weapons/Gunshot_smg.ogg'
|
||||
load_method = MAGAZINE
|
||||
magazine_type = /obj/item/ammo_magazine/a762
|
||||
|
||||
firemodes = list(
|
||||
list(name="short bursts", burst=5, move_delay=6, accuracy = list(0,-1,-1,-2,-2,-2,-3,-3), dispersion = list(0.6, 1.0, 1.0, 1.0, 1.2)),
|
||||
list(name="long bursts", burst=8, move_delay=8, accuracy = list(0,-1,-1,-2,-2,-2,-3,-3), dispersion = list(1.0, 1.0, 1.0, 1.0, 1.2)),
|
||||
)
|
||||
|
||||
var/cover_open = 0
|
||||
|
||||
/obj/item/weapon/gun/projectile/automatic/l6_saw/attack_self(mob/user as mob)
|
||||
|
||||
@@ -65,6 +65,12 @@
|
||||
caliber = "shotgun"
|
||||
origin_tech = "combat=3;materials=1"
|
||||
ammo_type = /obj/item/ammo_casing/shotgun/beanbag
|
||||
|
||||
burst_delay = 0
|
||||
firemodes = list(
|
||||
list(name="one barrel at a time", burst=1),
|
||||
list(name="both barrels at once", burst=2),
|
||||
)
|
||||
|
||||
/obj/item/weapon/gun/projectile/shotgun/doublebarrel/flare
|
||||
name = "signal shotgun"
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
var/p_x = 16
|
||||
var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
|
||||
|
||||
var/accuracy = 0
|
||||
var/dispersion = 0.0
|
||||
|
||||
var/damage = 10
|
||||
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here
|
||||
var/nodamage = 0 //Determines if the projectile will skip any damage inflictions
|
||||
@@ -100,6 +103,12 @@
|
||||
p_x = text2num(mouse_control["icon-x"])
|
||||
if(mouse_control["icon-y"])
|
||||
p_y = text2num(mouse_control["icon-y"])
|
||||
|
||||
//randomize clickpoint a bit based on dispersion
|
||||
if(dispersion)
|
||||
var/radius = round((dispersion*0.443)*world.icon_size*0.8) //0.443 = sqrt(pi)/4 = 2a, where a is the side length of a square that shares the same area as a circle with diameter = dispersion
|
||||
p_x = between(0, p_x + rand(-radius, radius), world.icon_size)
|
||||
p_y = between(0, p_y + rand(-radius, radius), world.icon_size)
|
||||
|
||||
//called to launch a projectile from a gun
|
||||
/obj/item/projectile/proc/launch(atom/target, mob/user, obj/item/weapon/gun/launcher, var/target_zone, var/x_offset=0, var/y_offset=0)
|
||||
@@ -147,24 +156,15 @@
|
||||
|
||||
yo = new_y - starting_loc.y
|
||||
xo = new_x - starting_loc.x
|
||||
setup_trajectory()
|
||||
|
||||
//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying.
|
||||
/obj/item/projectile/proc/attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0)
|
||||
if(!istype(target_mob))
|
||||
return
|
||||
//accuracy bonus from aiming
|
||||
if (istype(shot_from, /obj/item/weapon/gun))
|
||||
var/obj/item/weapon/gun/daddy = shot_from
|
||||
miss_modifier -= round(15*daddy.accuracy)
|
||||
|
||||
//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
|
||||
if (daddy.aim_targets && original in daddy.aim_targets)
|
||||
miss_modifier += -30
|
||||
|
||||
//roll to-hit
|
||||
miss_modifier = max(miss_modifier + 15*(distance-2), 0)
|
||||
miss_modifier = max(15*(distance-2) - round(15*accuracy) + miss_modifier, 0)
|
||||
var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1))
|
||||
if(!hit_zone)
|
||||
visible_message("<span class='notice'>\The [src] misses [target_mob] narrowly!</span>")
|
||||
@@ -314,10 +314,16 @@
|
||||
/obj/item/projectile/proc/before_move()
|
||||
|
||||
/obj/item/projectile/proc/setup_trajectory()
|
||||
// trajectory dispersion
|
||||
var/offset = 0
|
||||
if(dispersion)
|
||||
var/radius = round(dispersion*9, 1)
|
||||
offset = rand(-radius, radius)
|
||||
|
||||
// plot the initial trajectory
|
||||
trajectory = new()
|
||||
trajectory.setup(starting, original, pixel_x, pixel_y)
|
||||
|
||||
trajectory.setup(starting, original, pixel_x, pixel_y, angle_offset=offset)
|
||||
|
||||
// generate this now since all visual effects the projectile makes can use it
|
||||
effect_transform = new()
|
||||
effect_transform.Scale(trajectory.return_hypotenuse(), 1)
|
||||
|
||||
Reference in New Issue
Block a user