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"