From bb744805a058e6efc17d47115ce19d6ad885cc0b Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 20 Jan 2026 20:01:48 -0500 Subject: [PATCH] Make turrets only process when something "interesting" is in its range. (#94777) ## About The Pull Request Port from https://github.com/Monkestation/Monkestation2.0/pull/10330, as promised in https://github.com/tgstation/tgstation/pull/94757 This adds a proximity monitor to all turrets - they now track "interesting" targets within the turret's range, and the turret will only process if at least 1 thing is being tracked. The proximity monitor's checks for what's "interesting" are slightly less thorough than the turret's, and thus, it may still track something the turret will ignore regardless - but that's still better than the turret processing when nothing's nearby anyways. This also makes a new processing subsystem, SSturrets, as turret processing can be somewhat intensive in process, so like, let's not let them hog the SSmachines tick (plus, I honestly believe that splitting things into more specific processing subsystems is better for allowing the MC to manage tick time with more granularity). SSturrets has the same flags and wait as SSmachines. ## Why It's Good For The Game Should improve performance quite a bit - mainly, turrets on space ruins shouldn't waste tick time when there's nothing even near them. ## Changelog :cl: refactor: Refactored turrets so they only process if something potentially "interesting" is within its range. /:cl: --- .../subsystem/processing/turrets.dm | 6 ++ .../fields/turret_tracking.dm | 75 +++++++++++++++++++ .../machinery/porta_turret/portable_turret.dm | 34 ++++++--- tgstation.dme | 2 + 4 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 code/controllers/subsystem/processing/turrets.dm create mode 100644 code/datums/proximity_monitor/fields/turret_tracking.dm diff --git a/code/controllers/subsystem/processing/turrets.dm b/code/controllers/subsystem/processing/turrets.dm new file mode 100644 index 00000000000..34f128b5c22 --- /dev/null +++ b/code/controllers/subsystem/processing/turrets.dm @@ -0,0 +1,6 @@ +/// The subsystem used for portable turrets, as they're relatively more intensive compared to most other machines, so we don't want them hogging tick usage from everything else. +PROCESSING_SUBSYSTEM_DEF(turrets) + name = "Turret Processing" + flags = SS_NO_INIT | SS_KEEP_TIMING + wait = 2 SECONDS + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME diff --git a/code/datums/proximity_monitor/fields/turret_tracking.dm b/code/datums/proximity_monitor/fields/turret_tracking.dm new file mode 100644 index 00000000000..a24660d01f4 --- /dev/null +++ b/code/datums/proximity_monitor/fields/turret_tracking.dm @@ -0,0 +1,75 @@ +/// Proximity monitor that checks to see to see if anything worth shooting is within the bound of a turret. +/// Used so turrets don't waste time processing if nothing is near. +/datum/proximity_monitor/advanced/turret_tracking + edge_is_a_field = TRUE + /// Current list of things in the field. + var/list/atom/movable/tracking + /// Typecache of "interesting" objects. + var/static/list/interesting_typecache + +/datum/proximity_monitor/advanced/turret_tracking/New(atom/_host, range, _ignore_if_not_on_turf) + if(!istype(_host, /obj/machinery/porta_turret)) + CRASH("Turret trackers should only be used with /obj/machinery/porta_turret as a host!") + . = ..() + if(isnull(interesting_typecache)) + interesting_typecache = typecacheof(list( + /mob/living/basic, + /mob/living/carbon, + /mob/living/silicon/robot, + /mob/living/simple_animal, + /obj/structure/blob, + /obj/vehicle/sealed/mecha, + )) + +/datum/proximity_monitor/advanced/turret_tracking/Destroy() + . = ..() + LAZYNULL(tracking) // just to be sure + +/datum/proximity_monitor/advanced/turret_tracking/setup_field_turf(turf/target) + for(var/atom/movable/thing in target) + start_tracking(thing) + +/datum/proximity_monitor/advanced/turret_tracking/cleanup_field_turf(turf/target) + for(var/atom/movable/thing in target) + stop_tracking(thing) + +/datum/proximity_monitor/advanced/turret_tracking/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location) + start_tracking(movable) + +/datum/proximity_monitor/advanced/turret_tracking/field_turf_uncrossed(atom/movable/movable, turf/old_location, turf/new_location) + if(get_dist(new_location, host) > current_range) + stop_tracking(movable) + +/datum/proximity_monitor/advanced/turret_tracking/proc/start_tracking(atom/movable/thing) + if(QDELETED(src) || QDELETED(thing)) + return + if(thing.invisibility > SEE_INVISIBLE_LIVING || !is_type_in_typecache(thing, interesting_typecache)) + return + if(thing in tracking) // check this last + return + var/obj/machinery/porta_turret/turret = host + if(isliving(thing) && turret.in_faction(thing)) // hopefully no weird edge-cases where something's faction changes while its in range... right??? + return + LAZYADD(tracking, thing) + RegisterSignal(thing, COMSIG_QDELETING, PROC_REF(stop_tracking)) + refresh_turret_processing() + +/datum/proximity_monitor/advanced/turret_tracking/proc/stop_tracking(atom/movable/thing) + SIGNAL_HANDLER + if(isnull(thing) || !(thing in tracking)) + return + UnregisterSignal(thing, COMSIG_QDELETING) + // not using LAZYREMOVE so we can just do LAZYLEN once + tracking -= thing + if(!LAZYLEN(tracking)) + LAZYNULL(tracking) + refresh_turret_processing() + +/datum/proximity_monitor/advanced/turret_tracking/proc/refresh_turret_processing() + var/obj/machinery/porta_turret/turret = host + if(QDELETED(src) || QDELETED(turret)) + return + if(turret.check_should_process() != FALSE) // if check_should_process did something other than end processing, we have nothing more to do here. + return + if(turret.raised && !turret.always_up) // if its cover is up, then we'll pop the turret's cover down. + INVOKE_ASYNC(turret, TYPE_PROC_REF(/obj/machinery/porta_turret, popDown)) diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 738d5d15075..0b6215c8272 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -40,6 +40,8 @@ DEFINE_BITFIELD(turret_flags, list( armor_type = /datum/armor/machinery_porta_turret base_icon_state = "standard" blocks_emissive = EMISSIVE_BLOCK_UNIQUE + subsystem_type = /datum/controller/subsystem/processing/turrets + processing_flags = START_PROCESSING_MANUALLY // Same faction mobs will never be shot at, no matter the other settings faction = list(FACTION_TURRET) @@ -103,6 +105,8 @@ DEFINE_BITFIELD(turret_flags, list( var/datum/action/turret_toggle/toggle_action /// Mob that is remotely controlling the turret var/mob/remote_controller + /// Proximity tracker used to activate/deactivate the turret's processing. + var/datum/proximity_monitor/advanced/turret_tracking/tracker /// While the cooldown is still going on, it cannot be re-enabled. COOLDOWN_DECLARE(disabled_time) @@ -125,6 +129,8 @@ DEFINE_BITFIELD(turret_flags, list( spark_system.set_up(5, 0, src) spark_system.attach(src) + tracker = new(src, scan_range) + tracker.recalculate_field(full_recalc = TRUE) // manually call this so that our tracker var is set before we set anything up setup() if(has_cover) cover = new /obj/machinery/porta_turret_cover(loc) @@ -137,6 +143,16 @@ DEFINE_BITFIELD(turret_flags, list( AddElement(/datum/element/hostile_machine) +/obj/machinery/porta_turret/Destroy() + QDEL_NULL(tracker) + //deletes its own cover with it + QDEL_NULL(cover) + base = null + QDEL_NULL(stored_gun) + QDEL_NULL(spark_system) + remove_control() + return ..() + ///Toggles the turret on or off depending on the value of the turn_on arg. /obj/machinery/porta_turret/proc/toggle_on(turn_on = TRUE) if(on == turn_on) @@ -160,13 +176,18 @@ DEFINE_BITFIELD(turret_flags, list( INVOKE_ASYNC(src, PROC_REF(set_disabled), disrupt_duration) return TRUE +/// Checks to see if this should be processing, and starts/stops processing if so. +/// Returns TRUE if processing began, FALSE if processing ended, or null if the processing state was not changed. /obj/machinery/porta_turret/proc/check_should_process() if (datum_flags & DF_ISPROCESSING) - if (!on || !anchored || (machine_stat & BROKEN) || !powered()) + if (!on || !anchored || !LAZYLEN(tracker.tracking) || (machine_stat & BROKEN) || !powered()) end_processing() + return FALSE else - if (on && anchored && !(machine_stat & BROKEN) && powered()) + if (on && anchored && LAZYLEN(tracker.tracking) && !(machine_stat & BROKEN) && powered()) begin_processing() + return TRUE + return null /obj/machinery/porta_turret/update_icon_state() if(!anchored) @@ -223,15 +244,6 @@ DEFINE_BITFIELD(turret_flags, list( SIGNAL_HANDLER stored_gun = null -/obj/machinery/porta_turret/Destroy() - //deletes its own cover with it - QDEL_NULL(cover) - base = null - QDEL_NULL(stored_gun) - QDEL_NULL(spark_system) - remove_control() - return ..() - /obj/machinery/porta_turret/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) diff --git a/tgstation.dme b/tgstation.dme index 97d2f0d59fe..d0fc3aa433b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -824,6 +824,7 @@ #include "code\controllers\subsystem\processing\singulo.dm" #include "code\controllers\subsystem\processing\station.dm" #include "code\controllers\subsystem\processing\supermatter_cascade.dm" +#include "code\controllers\subsystem\processing\turrets.dm" #include "code\controllers\subsystem\processing\wet_floors.dm" #include "code\datums\alarm.dm" #include "code\datums\beam.dm" @@ -1867,6 +1868,7 @@ #include "code\datums\proximity_monitor\fields\heretic_arena.dm" #include "code\datums\proximity_monitor\fields\space_protection.dm" #include "code\datums\proximity_monitor\fields\timestop.dm" +#include "code\datums\proximity_monitor\fields\turret_tracking.dm" #include "code\datums\proximity_monitor\fields\void_storm.dm" #include "code\datums\proximity_monitor\fields\projectile_dampener\projectile_dampener.dm" #include "code\datums\proximity_monitor\fields\projectile_dampener\projectile_dampener_effects.dm"