Files
Bubberstation/code/datums/components/ranged_attacks.dm
SkyratBot b967c2787c [MIRROR] Give basic mob ranged attacks a cooldown [MDB IGNORE] (#23062)
* Give basic mob ranged attacks a cooldown (#77575)

## About The Pull Request

Atomised change from a different PR I am working on.
This changes the basic mob ranged attacks element into a component so
that it can also track an attack cooldown on the mob, preventing it from
firing until the cooldown is complete.
This was possible with simple mobs but wasn't kept going forwards when
converting things to basic ones.

## Why It's Good For The Game

Ideally player and mob behaviour should be unified as much as is
realistically possible. Currently mobs which are designed to fire a
powerful weapon slowly can blast as rapidly as the click cooldown if
placed under control of a player, which is not ideal.
This isn't currently aligned for melee attacks either but I will look at
that later.

## Changelog

🆑
fix: Player-controlled basic mobs with ranged attacks can now only fire
about as fast as AI-controlled ones.
/🆑

* Give basic mob ranged attacks a cooldown

* Modular update

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com>
2023-08-13 19:18:09 -04:00

79 lines
2.7 KiB
Plaintext

/**
* Configurable ranged attack for basic mobs.
*/
/datum/component/ranged_attacks
/// What kind of casing do we use to fire?
var/casing_type
/// What kind of projectile to we fire? Use only one of this or casing_type
var/projectile_type
/// Sound to play when we fire our projectile
var/projectile_sound
/// Time to wait between shots
var/cooldown_time
/// Tracks time between shots
COOLDOWN_DECLARE(fire_cooldown)
/datum/component/ranged_attacks/Initialize(
casing_type,
projectile_type,
projectile_sound = 'sound/weapons/gun/pistol/shot.ogg',
cooldown_time = 3 SECONDS,
)
. = ..()
if(!isbasicmob(parent))
return COMPONENT_INCOMPATIBLE
src.casing_type = casing_type
src.projectile_sound = projectile_sound
src.projectile_type = projectile_type
src.cooldown_time = cooldown_time
if (casing_type && projectile_type)
CRASH("Set both casing type and projectile type in [parent]'s ranged attacks component! uhoh! stinky!")
if (!casing_type && !projectile_type)
CRASH("Set neither casing type nor projectile type in [parent]'s ranged attacks component! What are they supposed to be attacking with, air?")
/datum/component/ranged_attacks/RegisterWithParent()
. = ..()
RegisterSignal(parent, COMSIG_MOB_ATTACK_RANGED, PROC_REF(fire_ranged_attack))
ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type)
/datum/component/ranged_attacks/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, COMSIG_MOB_ATTACK_RANGED)
REMOVE_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type)
/datum/component/ranged_attacks/proc/fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers)
SIGNAL_HANDLER
if (!COOLDOWN_FINISHED(src, fire_cooldown))
return
COOLDOWN_START(src, fire_cooldown, cooldown_time)
INVOKE_ASYNC(src, PROC_REF(async_fire_ranged_attack), firer, target, modifiers)
/// Actually fire the damn thing
/datum/component/ranged_attacks/proc/async_fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers)
playsound(firer, projectile_sound, 100, TRUE)
var/turf/startloc = get_turf(firer)
if(casing_type)
var/obj/item/ammo_casing/casing = new casing_type(startloc)
var/target_zone
if(ismob(target))
var/mob/target_mob = target
target_zone = target_mob.get_random_valid_zone()
else
target_zone = ran_zone()
casing.fire_casing(target, firer, null, null, null, target_zone, 0, firer)
casing.AddElement(/datum/element/temporary_atom, 30 SECONDS)
return
var/obj/projectile/bullet = new projectile_type(startloc)
bullet.starting = startloc
bullet.firer = firer
bullet.fired_from = firer
bullet.yo = target.y - startloc.y
bullet.xo = target.x - startloc.x
bullet.original = target
bullet.preparePixelProjectile(target, firer)
bullet.fire()