From b967c2787c79e4737bc9dcd84269606292fb2d6d Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Mon, 14 Aug 2023 01:18:09 +0200 Subject: [PATCH] [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 :cl: fix: Player-controlled basic mobs with ranged attacks can now only fire about as fast as AI-controlled ones. /:cl: * Give basic mob ranged attacks a cooldown * Modular update --------- Co-authored-by: Jacquerel Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com> --- .../basic_subtrees/simple_attack_target.dm | 2 +- code/datums/components/ranged_attacks.dm | 78 +++++++++++++++++++ code/datums/elements/ranged_attacks.dm | 59 -------------- .../living/basic/ruin_defender/stickman.dm | 2 +- .../basic/space_fauna/hivebot/_hivebot.dm | 5 +- .../mob/living/basic/syndicate/russian.dm | 2 +- .../mob/living/basic/syndicate/syndicate.dm | 8 +- .../mob/living/basic/vermin/cockroach.dm | 2 +- .../mothership_astrum/mob.dm | 2 +- tgstation.dme | 2 +- 10 files changed, 95 insertions(+), 67 deletions(-) create mode 100644 code/datums/components/ranged_attacks.dm delete mode 100644 code/datums/elements/ranged_attacks.dm diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm index 46325a37b9f..90379fcac0a 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm @@ -13,7 +13,7 @@ melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/average_speed /datum/ai_planning_subtree/basic_ranged_attack_subtree - operational_datums = list(/datum/element/ranged_attacks) + operational_datums = list(/datum/component/ranged_attacks) var/datum/ai_behavior/basic_ranged_attack/ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack /datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) diff --git a/code/datums/components/ranged_attacks.dm b/code/datums/components/ranged_attacks.dm new file mode 100644 index 00000000000..a61eff468ec --- /dev/null +++ b/code/datums/components/ranged_attacks.dm @@ -0,0 +1,78 @@ +/** + * 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() diff --git a/code/datums/elements/ranged_attacks.dm b/code/datums/elements/ranged_attacks.dm deleted file mode 100644 index 2df0a9189ff..00000000000 --- a/code/datums/elements/ranged_attacks.dm +++ /dev/null @@ -1,59 +0,0 @@ -///This proc is used by basic mobs to give them a simple ranged attack! In theory this could be extended to -/datum/element/ranged_attacks - element_flags = ELEMENT_BESPOKE - argument_hash_start_idx = 2 - var/casingtype = /obj/item/ammo_casing/glockroach - var/projectilesound = 'sound/weapons/gun/pistol/shot.ogg' - var/projectiletype - -/datum/element/ranged_attacks/Attach(atom/movable/target, casingtype, projectilesound, projectiletype) - . = ..() - if(!isbasicmob(target)) - return COMPONENT_INCOMPATIBLE - - src.casingtype = casingtype - src.projectilesound = projectilesound - src.projectiletype = projectiletype - - RegisterSignal(target, COMSIG_MOB_ATTACK_RANGED, PROC_REF(fire_ranged_attack)) - - if(casingtype && projectiletype) - CRASH("Set both casing type and projectile type in [target]'s ranged attacks element! uhoh! stinky!") - - ADD_TRAIT(target, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) - -/datum/element/ranged_attacks/Detach(datum/target) - UnregisterSignal(target, COMSIG_MOB_ATTACK_RANGED) - return ..() - -/datum/element/ranged_attacks/proc/fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) - SIGNAL_HANDLER - INVOKE_ASYNC(src, PROC_REF(async_fire_ranged_attack), firer, target, modifiers) - - -/datum/element/ranged_attacks/proc/async_fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) - var/turf/startloc = get_turf(firer) - - if(casingtype) - var/obj/item/ammo_casing/casing = new casingtype(startloc) - playsound(firer, projectilesound, 100, TRUE) - 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) - - else if(projectiletype) - var/obj/projectile/P = new projectiletype(startloc) - playsound(firer, projectilesound, 100, TRUE) - P.starting = startloc - P.firer = firer - P.fired_from = firer - P.yo = target.y - startloc.y - P.xo = target.x - startloc.x - P.original = target - P.preparePixelProjectile(target, firer) - P.fire() diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.dm b/code/modules/mob/living/basic/ruin_defender/stickman.dm index 40be390ab1c..8d0a5ab0cde 100644 --- a/code/modules/mob/living/basic/ruin_defender/stickman.dm +++ b/code/modules/mob/living/basic/ruin_defender/stickman.dm @@ -78,7 +78,7 @@ . = ..() var/static/list/stickman_drops = list(/obj/item/gun/ballistic/automatic/pistol/stickman) AddElement(/datum/element/death_drops, stickman_drops) - AddElement(/datum/element/ranged_attacks, /obj/item/ammo_casing/c9mm, 'sound/misc/bang.ogg') + AddComponent(/datum/component/ranged_attacks, casing_type = /obj/item/ammo_casing/c9mm, projectile_sound = 'sound/misc/bang.ogg', cooldown_time = 5 SECONDS) /datum/ai_controller/basic_controller/stickman/ranged planning_subtrees = list( diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm b/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm index 83a1135ac05..df4ed505f3d 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/_hivebot.dm @@ -34,6 +34,8 @@ ai_controller = /datum/ai_controller/basic_controller/hivebot ///does this type do range attacks? var/ranged_attacker = FALSE + /// How often can we shoot? + var/ranged_attack_cooldown = 3 SECONDS /mob/living/basic/hivebot/Initialize(mapload) @@ -43,7 +45,7 @@ AddElement(/datum/element/appearance_on_aggro, overlay_icon = icon, overlay_state = "[initial(icon_state)]_attack") if(!ranged_attacker) return - AddElement(/datum/element/ranged_attacks, /obj/item/ammo_casing/hivebot) + AddComponent(/datum/component/ranged_attacks, /obj/item/ammo_casing/hivebot, cooldown_time = ranged_attack_cooldown) /mob/living/basic/hivebot/death(gibbed) do_sparks(number = 3, cardinal_only = TRUE, source = src) @@ -64,6 +66,7 @@ icon_dead = "ranged" ranged_attacker = TRUE ai_controller = /datum/ai_controller/basic_controller/hivebot/ranged/rapid + ranged_attack_cooldown = 1.5 SECONDS /mob/living/basic/hivebot/strong name = "strong hivebot" diff --git a/code/modules/mob/living/basic/syndicate/russian.dm b/code/modules/mob/living/basic/syndicate/russian.dm index de6c91a4dae..2de74b9b4d5 100644 --- a/code/modules/mob/living/basic/syndicate/russian.dm +++ b/code/modules/mob/living/basic/syndicate/russian.dm @@ -32,7 +32,7 @@ /mob/living/basic/syndicate/russian/ranged/Initialize(mapload) . = ..() - AddElement(/datum/element/ranged_attacks, casingtype, projectilesound) + AddComponent(/datum/component/ranged_attacks, casing_type = casingtype, projectile_sound = projectilesound, cooldown_time = 1 SECONDS) /mob/living/basic/syndicate/russian/ranged/lootless loot = list() diff --git a/code/modules/mob/living/basic/syndicate/syndicate.dm b/code/modules/mob/living/basic/syndicate/syndicate.dm index 25124d87da4..549dbf476d0 100644 --- a/code/modules/mob/living/basic/syndicate/syndicate.dm +++ b/code/modules/mob/living/basic/syndicate/syndicate.dm @@ -134,12 +134,16 @@ loot = list(/obj/effect/gibspawner/human) ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged r_hand = /obj/item/gun/ballistic/automatic/pistol + /// Type of bullet we use var/casingtype = /obj/item/ammo_casing/c9mm + /// Sound to play when firing weapon var/projectilesound = 'sound/weapons/gun/pistol/shot.ogg' + /// Time between taking shots + var/ranged_cooldown = 1 SECONDS /mob/living/basic/syndicate/ranged/Initialize(mapload) . = ..() - AddElement(/datum/element/ranged_attacks, casingtype, projectilesound) + AddComponent(/datum/component/ranged_attacks, casing_type = casingtype, projectile_sound = projectilesound, cooldown_time = ranged_cooldown) /mob/living/basic/syndicate/ranged/infiltrator //shuttle loan event projectilesound = 'sound/weapons/gun/smg/shot_suppressed.ogg' @@ -168,6 +172,7 @@ casingtype = /obj/item/ammo_casing/c45 projectilesound = 'sound/weapons/gun/smg/shot.ogg' ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged/burst + ranged_cooldown = 3 SECONDS r_hand = /obj/item/gun/ballistic/automatic/c20r /mob/living/basic/syndicate/ranged/smg/pilot //caravan ambush ruin @@ -197,6 +202,7 @@ /mob/living/basic/syndicate/ranged/shotgun casingtype = /obj/item/ammo_casing/shotgun/buckshot //buckshot (up to 72.5 brute) fired in a two-round burst ai_controller = /datum/ai_controller/basic_controller/syndicate/ranged/shotgunner + ranged_cooldown = 3 SECONDS r_hand = /obj/item/gun/ballistic/shotgun/bulldog /mob/living/basic/syndicate/ranged/shotgun/space diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach.dm index d7b2d8aade5..90e5bd53d2d 100644 --- a/code/modules/mob/living/basic/vermin/cockroach.dm +++ b/code/modules/mob/living/basic/vermin/cockroach.dm @@ -96,7 +96,7 @@ /mob/living/basic/cockroach/glockroach/Initialize(mapload) . = ..() - AddElement(/datum/element/ranged_attacks, /obj/item/ammo_casing/glockroach) + AddComponent(/datum/component/ranged_attacks, casing_type = /obj/item/ammo_casing/glockroach, cooldown_time = 1 SECONDS) /datum/ai_controller/basic_controller/cockroach/glockroach planning_subtrees = list( diff --git a/modular_skyrat/modules/awaymissions_skyrat/mothership_astrum/mob.dm b/modular_skyrat/modules/awaymissions_skyrat/mothership_astrum/mob.dm index 1a1b468e3f4..6efc58b3e75 100644 --- a/modular_skyrat/modules/awaymissions_skyrat/mothership_astrum/mob.dm +++ b/modular_skyrat/modules/awaymissions_skyrat/mothership_astrum/mob.dm @@ -85,7 +85,7 @@ /mob/living/basic/abductor/ranged/Initialize(mapload) . = ..() - AddElement(/datum/element/ranged_attacks, null, 'sound/weapons/laser.ogg', /obj/projectile/beam/laser) + AddComponent(/datum/component/ranged_attacks, projectile_sound = 'sound/weapons/laser.ogg', projectile_type = /obj/projectile/beam/laser) // Tankier variant diff --git a/tgstation.dme b/tgstation.dme index e429f5859af..342eb8ff344 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1103,6 +1103,7 @@ #include "code\datums\components\radiation_countdown.dm" #include "code\datums\components\radioactive_emitter.dm" #include "code\datums\components\radioactive_exposure.dm" +#include "code\datums\components\ranged_attacks.dm" #include "code\datums\components\reagent_refiller.dm" #include "code\datums\components\redirect_attack_hand_from_turf.dm" #include "code\datums\components\reflection.dm" @@ -1388,7 +1389,6 @@ #include "code\datums\elements\radiation_protected_clothing.dm" #include "code\datums\elements\radioactive.dm" #include "code\datums\elements\ranged_armour.dm" -#include "code\datums\elements\ranged_attacks.dm" #include "code\datums\elements\relay_attackers.dm" #include "code\datums\elements\ridable.dm" #include "code\datums\elements\rust.dm"