Skip to content

Commit

Permalink
Vehicle autofire (#4959)
Browse files Browse the repository at this point in the history
# About the pull request

Convert vehicle hardpoints from using their bespoke firing system to one
structured closely on handheld guns and deployables such as the M2C. Now
using the `autofire` component. Much like handheld weapons it is capable
of different firemodes (semi/burst/auto) and changing targets during
fire.

Hardpoints were converted to match their old effectiveness as closely as
possible; this is intended as a quality of life improvement, not a
rebalance. Damage, AP, range, ammo, etc were not touched.

Fire rates were copied over directly. Single-fire weapons with long
delays were made semi-auto (e.g. LTB), and those with short delays were
made full-auto (e.g. autocannon). Burst-fire weapons with significant
extra delays after the burst remained burst-fire (cupola, smokescreen),
and the rest were converted to full-auto (e.g. dual cannon). While
changing firemodes is easily implemented, no weapon seemed a good
candidate for more than one firemode and so that is omitted for now.

Scatter was approximated. The existing `accuracy` functioned as a
percent chance the shot would stray one tile from the target. Gun-style
`scatter` is instead a cone of fire in degrees. No direct conversion is
possible, so scatter values are roughly set such that firing at a tile
at the edge of the screen should "feel" about as accurate. Closer ranges
would experience less spread than before, longer ranges more.

The buffing weapon sensor module was adjusted to work with the new
firing system, and effects hardpoint scatter angle and firing rate.
Vehicle buffs still use multipliers instead of adding/subtracting as
handheld guns do, as a flat +/- adjustment to fire delay would have a
significantly different effect on slow firing weapons (e.g. LTB) vs fast
firing (e.g. autocannon). One major difference is that burstfire delays
are effected and buffs increases the burst density. Before, there was a
single cooldown initiated at the start of the burst, and only that
cooldown was modified by the buff. Now, since the inter-burst delay is
needed by the `autofire` component both the inter-burst delay and the
after-burst delay are modified by buffs.

Activating non-selected hardpoints was removed as not compatible. The
issue is that tracking a single click's modifiers is no longer
sufficient, it has to track through the whole mousedown-to-mouseup
period and the user can change multiple click modifiers in that time. I
could not find a method that was satisfactory without a much bigger
overhaul of vehicle controls than I'd like to take on in a PR not meant
for it. I'm sure it can be done, but that brings up the question of if
that's even the control scheme we'd want, in a PR that was never meant
to ask that question let alone answer it.

<!-- Remove this text and explain what the purpose of your PR is.

Mention if you have tested your changes. If you changed a map, make sure
you used the mapmerge tool.
If this is an Issue Correction, you can type "Fixes Issue #169420" to
link the PR to the corresponding Issue number #169420.

Remember: something that is self-evident to you might not be to others.
Explain your rationale fully, even if you feel it goes without saying.
-->

# Explain why it's good for the game

Vehicle weapons using `gun`-like code makes them easier and more
familiar to use, and more code commonality makes maintenance just a
little bit easier.
# Testing Photographs and Procedure
<details>
<summary>Screenshots & Videos</summary>

Put screenshots and videos here with an empty line between the
screenshots and the `<details>` tags.

</details>


# Changelog
:cl:
refactor: vehicle weapons can fire full-auto
del: no more controls for firing vehicle non-selected weapons
/:cl:
  • Loading branch information
Doubleumc authored Dec 6, 2023
1 parent f1cfdbd commit f4f334d
Show file tree
Hide file tree
Showing 21 changed files with 550 additions and 571 deletions.
441 changes: 288 additions & 153 deletions code/modules/vehicles/hardpoints/hardpoint.dm

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions code/modules/vehicles/hardpoints/holder/holder.dm
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,21 @@
for(var/obj/item/hardpoint/H in hardpoints)
H.take_damage(damage)

/obj/item/hardpoint/holder/on_install(obj/vehicle/multitile/V)
for(var/obj/item/hardpoint/HP in hardpoints)
HP.owner = V
return
/obj/item/hardpoint/holder/on_install(obj/vehicle/multitile/vehicle)
..()
if(!vehicle) //in loose holder
return
for(var/obj/item/hardpoint/hardpoint in hardpoints)
hardpoint.owner = vehicle
hardpoint.on_install(vehicle)

/obj/item/hardpoint/holder/on_uninstall(obj/vehicle/multitile/vehicle)
if(!vehicle) //in loose holder
return
for(var/obj/item/hardpoint/hardpoint in hardpoints)
hardpoint.on_uninstall(vehicle)
hardpoint.owner = null
..()

/obj/item/hardpoint/holder/proc/can_install(obj/item/hardpoint/H)
// Can only have 1 hardpoint of each slot type
Expand Down Expand Up @@ -121,16 +132,17 @@
H.forceMove(src)
LAZYADD(hardpoints, H)

H.on_install(owner)
H.rotate(turning_angle(H.dir, dir))

/obj/item/hardpoint/holder/proc/remove_hardpoint(obj/item/hardpoint/H, turf/uninstall_to)
if(!hardpoints)
return
hardpoints -= H
H.forceMove(uninstall_to ? uninstall_to : get_turf(src))

H.on_uninstall(owner)
H.reset_rotation()

hardpoints -= H
H.owner = null

if(H.health <= 0)
Expand Down
46 changes: 18 additions & 28 deletions code/modules/vehicles/hardpoints/holder/tank_turret.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
density = TRUE //come on, it's huge

activatable = TRUE
cooldown = 150
accuracy = 0.8

ammo = new /obj/item/ammo_magazine/hardpoint/turret_smoke
max_clips = 2
Expand Down Expand Up @@ -60,6 +58,15 @@
// Used during the windup
var/rotating = FALSE

scatter = 4
gun_firemode = GUN_FIREMODE_BURSTFIRE
gun_firemode_list = list(
GUN_FIREMODE_BURSTFIRE,
)
burst_amount = 2
burst_delay = 1.0 SECONDS
extra_delay = 13.0 SECONDS

/obj/item/hardpoint/holder/tank_turret/update_icon()
var/broken = (health <= 0)
icon_state = "tank_turret_[broken]"
Expand Down Expand Up @@ -182,12 +189,7 @@
user.client.pixel_x = -1 * AM.view_tile_offset * 32
user.client.pixel_y = 0

/obj/item/hardpoint/holder/tank_turret/fire(mob/user, atom/A)
if(ammo.current_rounds <= 0)
return

next_use = world.time + cooldown

/obj/item/hardpoint/holder/tank_turret/try_fire(atom/target, mob/living/user, params)
var/turf/L
var/turf/R
switch(owner.dir)
Expand All @@ -204,26 +206,14 @@
L = locate(owner.x - 4, owner.y + 2, owner.z)
R = locate(owner.x - 4, owner.y - 2, owner.z)

if(LAZYLEN(activation_sounds))
playsound(get_turf(src), pick(activation_sounds), 60, 1)
fire_projectile(user, L)
if(shots_fired)
target = R
else
target = L

sleep(10)
return ..()

if(LAZYLEN(activation_sounds))
playsound(get_turf(src), pick(activation_sounds), 60, 1)
fire_projectile(user, R)

to_chat(user, SPAN_WARNING("Smoke Screen uses left: <b>[SPAN_HELPFUL(ammo ? ammo.current_rounds / 2 : 0)]/[SPAN_HELPFUL(ammo ? ammo.max_rounds / 2 : 0)]</b> | Mags: <b>[SPAN_HELPFUL(LAZYLEN(backup_clips))]/[SPAN_HELPFUL(max_clips)]</b>"))

/obj/item/hardpoint/holder/tank_turret/fire_projectile(mob/user, atom/A)
set waitfor = 0

var/turf/origin_turf = get_turf(src)
origin_turf = locate(origin_turf.x + origins[1], origin_turf.y + origins[2], origin_turf.z)
/obj/item/hardpoint/holder/tank_turret/get_origin_turf()
var/origin_turf = ..()
origin_turf = get_step(get_step(origin_turf, owner.dir), owner.dir) //this should get us tile in front of tank to prevent grenade being stuck under us.

var/obj/projectile/P = generate_bullet(user, origin_turf)
SEND_SIGNAL(P, COMSIG_BULLET_USER_EFFECTS, owner.seats[VEHICLE_GUNNER])
P.fire_at(A, owner.seats[VEHICLE_GUNNER], src, get_dist(origin_turf, A) + 1, P.ammo.shell_speed)
ammo.current_rounds--
return origin_turf
9 changes: 7 additions & 2 deletions code/modules/vehicles/hardpoints/primary/autocannon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
activation_sounds = list('sound/weapons/vehicles/autocannon_fire.ogg')

health = 500
cooldown = 7
accuracy = 0.98
firing_arc = 60

origins = list(0, -3)
Expand All @@ -23,3 +21,10 @@
"4" = list(32, 0),
"8" = list(-32, 0)
)

scatter = 1
gun_firemode = GUN_FIREMODE_AUTOMATIC
gun_firemode_list = list(
GUN_FIREMODE_AUTOMATIC,
)
fire_delay = 0.7 SECONDS
29 changes: 7 additions & 22 deletions code/modules/vehicles/hardpoints/primary/dual_cannon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
damage_multiplier = 0.2

health = 500
cooldown = 7
accuracy = 0.98
firing_arc = 60
var/burst_amount = 2

origins = list(0, -2)

Expand All @@ -33,27 +30,15 @@
"8" = list(14, 9)
)

scatter = 1
gun_firemode = GUN_FIREMODE_AUTOMATIC
gun_firemode_list = list(
GUN_FIREMODE_AUTOMATIC,
)
fire_delay = 0.3 SECONDS

/obj/item/hardpoint/primary/dualcannon/set_bullet_traits()
..()
LAZYADD(traits_to_give, list(
BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
))

/obj/item/hardpoint/primary/dualcannon/fire(mob/user, atom/A)
if(ammo.current_rounds <= 0)
return

next_use = world.time + cooldown * owner.misc_multipliers["cooldown"]

for(var/bullets_fired = 1, bullets_fired <= burst_amount, bullets_fired++)
var/atom/T = A
if(!prob((accuracy * 100) / owner.misc_multipliers["accuracy"]))
T = get_step(get_turf(A), pick(GLOB.cardinals))
if(LAZYLEN(activation_sounds))
playsound(get_turf(src), pick(activation_sounds), 60, 1)
fire_projectile(user, T)
if(ammo.current_rounds <= 0)
break
if(bullets_fired < burst_amount) //we need to sleep only if there are more bullets to shoot in the burst
sleep(3)
to_chat(user, SPAN_WARNING("[src] Ammo: <b>[SPAN_HELPFUL(ammo ? ammo.current_rounds : 0)]/[SPAN_HELPFUL(ammo ? ammo.max_rounds : 0)]</b> | Mags: <b>[SPAN_HELPFUL(LAZYLEN(backup_clips))]/[SPAN_HELPFUL(max_clips)]</b>"))
37 changes: 9 additions & 28 deletions code/modules/vehicles/hardpoints/primary/flamer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
activation_sounds = list('sound/weapons/vehicles/flamethrower.ogg')

health = 400
cooldown = 20
accuracy = 0.75
firing_arc = 90

origins = list(0, -3)
Expand All @@ -26,36 +24,19 @@

use_muzzle_flash = FALSE

scatter = 5
fire_delay = 2.0 SECONDS

/obj/item/hardpoint/primary/flamer/set_bullet_traits()
..()
LAZYADD(traits_to_give, list(
BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
))

/obj/item/hardpoint/primary/flamer/can_activate(mob/user, atom/A)
if(!..())
return FALSE

var/turf/origin_turf = get_turf(src)
origin_turf = locate(origin_turf.x + origins[1], origin_turf.y + origins[2], origin_turf.z)
if(origin_turf == get_turf(A))
return FALSE

return TRUE

/obj/item/hardpoint/primary/flamer/fire_projectile(mob/user, atom/A)
set waitfor = 0

var/turf/origin_turf = get_turf(src)
origin_turf = locate(origin_turf.x + origins[1], origin_turf.y + origins[2], origin_turf.z)

var/range = get_dist(origin_turf, A) + 1

var/obj/projectile/P = generate_bullet(user, origin_turf)
SEND_SIGNAL(P, COMSIG_BULLET_USER_EFFECTS, owner.seats[VEHICLE_GUNNER])
P.fire_at(A, owner.seats[VEHICLE_GUNNER], src, range < P.ammo.max_range ? range : P.ammo.max_range, P.ammo.shell_speed)

if(use_muzzle_flash)
muzzle_flash(Get_Angle(owner, A))
/obj/item/hardpoint/primary/flamer/try_fire(target, user, params)
var/turf/origin_turf = get_origin_turf()
if(origin_turf == get_turf(target))
to_chat(user, SPAN_WARNING("The target is too close."))
return NONE

ammo.current_rounds--
return ..()
5 changes: 3 additions & 2 deletions code/modules/vehicles/hardpoints/primary/ltb.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
activation_sounds = list('sound/weapons/vehicles/cannon_fire1.ogg', 'sound/weapons/vehicles/cannon_fire2.ogg')

health = 500
cooldown = 200
accuracy = 0.97
firing_arc = 60

origins = list(0, -3)
Expand All @@ -30,3 +28,6 @@
"4" = list(89, -4),
"8" = list(-89, -4)
)

scatter = 2
fire_delay = 20.0 SECONDS
86 changes: 48 additions & 38 deletions code/modules/vehicles/hardpoints/primary/minigun.dm
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
disp_icon_state = "ltaaap_minigun"

health = 350
cooldown = 8
accuracy = 0.6
firing_arc = 90

origins = list(0, -3)
Expand All @@ -30,46 +28,58 @@
"8" = list(-77, 0)
)

//changed minigun mechanic so instead of having lowered cooldown with each shot it now has increased burst size.
//While it's still spammy, user doesn't have to click as fast as possible anymore and has margin of 2 seconds before minigun will start slowing down

var/chained_shots = 1 //how many quick succession shots we've fired, 1 by default
var/last_shot_time = 0 //when was last shot fired, after 3 seconds we stop barrel
var/list/chain_bursts = list(1, 1, 2, 2, 3, 3, 3, 4, 4, 4) //how many shots per click we do
scatter = 7
gun_firemode = GUN_FIREMODE_AUTOMATIC
gun_firemode_list = list(
GUN_FIREMODE_AUTOMATIC,
)
fire_delay = 0.8 SECONDS //base fire rate, modified by stage_delay_mult

activation_sounds = list('sound/weapons/gun_minigun.ogg')
/// Active firing time to reach max spin_stage.
var/spinup_time = 8 SECONDS
/// Grace period before losing spin_stage.
var/spindown_grace_time = 2 SECONDS
COOLDOWN_DECLARE(spindown_grace_cooldown)
/// Cooldown time to reach min spin_stage.
var/spindown_time = 3 SECONDS
/// Index of stage_rate.
var/spin_stage = 1
/// Shots fired per fire_delay at a particular spin_stage.
var/list/stage_rate = list(1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5)
/// Fire delay multiplier for current spin_stage.
var/stage_delay_mult = 1
/// When it was last fired, related to world.time.
var/last_fired = 0

/obj/item/hardpoint/primary/minigun/fire(mob/user, atom/A)
/obj/item/hardpoint/primary/minigun/set_fire_delay(value)
fire_delay = value
SEND_SIGNAL(src, COMSIG_GUN_AUTOFIREDELAY_MODIFIED, fire_delay * stage_delay_mult)

var/S = 'sound/weapons/vehicles/minigun_stop.ogg'
//check how much time since last shot. 2 seconds are grace period before minigun starts to lose rotation momentum
var/t = world.time - last_shot_time - 2 SECONDS
t = round(t / 10)
if(t > 0)
chained_shots = max(chained_shots - t * 3, 1) //we lose 3 chained_shots per second
else
if(chained_shots < 11)
chained_shots++
S = 'sound/weapons/vehicles/minigun_loop.ogg'
/obj/item/hardpoint/primary/minigun/set_fire_cooldown()
calculate_stage_delay_mult() //needs to check grace_cooldown before refreshed
last_fired = world.time
COOLDOWN_START(src, spindown_grace_cooldown, spindown_grace_time)
COOLDOWN_START(src, fire_cooldown, fire_delay * stage_delay_mult)

if(chained_shots == 1)
playsound(get_turf(src), 'sound/weapons/vehicles/minigun_start.ogg', 40, 1)
/obj/item/hardpoint/primary/minigun/proc/calculate_stage_delay_mult()
var/stage_rate_len = stage_rate.len
var/delta_time = world.time - last_fired

next_use = world.time + cooldown * owner.misc_multipliers["cooldown"]

//how many rounds we will shoot in this burst
if(chained_shots > LAZYLEN(chain_bursts)) //5 shots at maximum rotation
t = 5
var/old_spin_stage = spin_stage
if(auto_firing || burst_firing) //spinup if continuing fire
var/delta_stage = delta_time * (stage_rate_len - 1)
spin_stage += delta_stage / spinup_time
else if(COOLDOWN_FINISHED(src, spindown_grace_cooldown)) //spindown if initiating fire after grace
var/delta_stage = (delta_time - spindown_grace_time) * (stage_rate_len - 1)
spin_stage -= delta_stage / spindown_time
else
t = LAZYACCESS(chain_bursts, chained_shots)
for(var/i = 1; i <= t; i++)
var/atom/T = A
if(!prob((accuracy * 100) / owner.misc_multipliers["accuracy"]))
T = get_step(get_turf(T), pick(GLOB.cardinals))
fire_projectile(user, T)
if(ammo.current_rounds <= 0)
break
sleep(2)
to_chat(user, SPAN_WARNING("[src] Ammo: <b>[SPAN_HELPFUL(ammo ? ammo.current_rounds : 0)]/[SPAN_HELPFUL(ammo ? ammo.max_rounds : 0)]</b> | Mags: <b>[SPAN_HELPFUL(LAZYLEN(backup_clips))]/[SPAN_HELPFUL(max_clips)]</b>"))
return
spin_stage = Clamp(spin_stage, 1, stage_rate_len)

var/old_stage_rate = stage_rate[Floor(old_spin_stage)]
var/new_stage_rate = stage_rate[Floor(spin_stage)]

playsound(get_turf(src), S, 40, 1)
last_shot_time = world.time
if(old_stage_rate != new_stage_rate)
stage_delay_mult = 1 / new_stage_rate
SEND_SIGNAL(src, COMSIG_GUN_AUTOFIREDELAY_MODIFIED, fire_delay * stage_delay_mult)
Loading

0 comments on commit f4f334d

Please sign in to comment.