From 897b7faab2dfb46ab72692f1b77e911755df9925 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Thu, 5 Oct 2023 05:49:41 +0200 Subject: [PATCH] [MIRROR] Adds a passive scan to the Active Sonar module [MDB IGNORE] (#24117) * Adds a passive scan to the Active Sonar module (#78734) ## About The Pull Request Adds a passive, radial scan to the Active Sonar module. This will scan a 1/8th slice around the player for any creatures, and place a much smaller marker over them for the player to see. These small markers do not update their location when the scanned creature moves, unlike the normal markers. Activating the module initiates the normal full scan, which scans the entire radius at once rather than just a slice. Doing so will put _both_ scans in cooldown. https://github.com/tgstation/tgstation/assets/66052067/96226090-fa32-42d5-99f4-a8dbdc36cf98 ## Why It's Good For The Game Honestly I still felt the Active Sonar was a little weak even after I tried to buff it a little. I wanted to initially find a way to make the large scan effects smoothly move with whoever you're following, but since I couldn't manage to figure that out instead I went with a recommendation from ninjanomnom to make a radial scan. I think this is far more interesting either way ## Changelog :cl: Wallem add: Buffs the Active Sonar module with a radial scan, and makes the power costs more in-line with other modules. /:cl: * Adds a passive scan to the Active Sonar module --------- Co-authored-by: Wallem <66052067+Wallemations@users.noreply.github.com> --- .../temporary_visuals/miscellaneous.dm | 21 +++- code/modules/mod/modules/modules_security.dm | 94 ++++++++++++++++-- icons/effects/effects.dmi | Bin 949901 -> 949996 bytes 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index e88753d25c1..c736aac35d8 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -594,11 +594,22 @@ var/datum/weakref/pinged_person /// The icon state applied to the image created for this ping. var/real_icon_state = "sonar_ping" + /// Does the visual follow the creature? + var/follow_creature = TRUE + /// Creature's X & Y coords, which can either be overridden or kept the same depending on follow_creature. + var/creature_x + var/creature_y -/obj/effect/temp_visual/sonar_ping/Initialize(mapload, mob/living/looker, mob/living/creature) +/obj/effect/temp_visual/sonar_ping/Initialize(mapload, mob/living/looker, mob/living/creature, ping_state, follow_creatures = TRUE) . = ..() if(!looker || !creature) return INITIALIZE_HINT_QDEL + if(ping_state) + real_icon_state = ping_state + follow_creature = follow_creatures + creature_x = creature.x + creature_y = creature.y + modsuit_image = image(icon = icon, loc = looker.loc, icon_state = real_icon_state, layer = ABOVE_ALL_MOB_LAYER, pixel_x = ((creature.x - looker.x) * 32), pixel_y = ((creature.y - looker.y) * 32)) modsuit_image.plane = ABOVE_LIGHTING_PLANE SET_PLANE_EXPLICIT(modsuit_image, ABOVE_LIGHTING_PLANE, creature) @@ -631,8 +642,12 @@ if(isnull(looker) || isnull(creature)) return PROCESS_KILL modsuit_image.loc = looker.loc - modsuit_image.pixel_x = ((creature.x - looker.x) * 32) - modsuit_image.pixel_y = ((creature.y - looker.y) * 32) + // Long pings follow, short pings stay put. We still need to update for looker.x&y though + if(follow_creature) + creature_y = creature.y + creature_x = creature.x + modsuit_image.pixel_x = ((creature_x - looker.x) * 32) + modsuit_image.pixel_y = ((creature_y - looker.y) * 32) /obj/effect/temp_visual/block //color is white by default, set to whatever is needed name = "blocking glow" diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index 2c7ae6821fe..7e331433b4f 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -361,13 +361,95 @@ /obj/item/mod/module/active_sonar name = "MOD active sonar" desc = "Ancient tech from the 20th century, this module uses sonic waves to detect living creatures within the user's radius. \ + Its basic function slowly scans around the user for any bio-signatures, however it can be overclocked to scan everywhere at once.\ Its loud ping is much harder to hide in an indoor station than in the outdoor operations it was designed for." icon_state = "active_sonar" module_type = MODULE_USABLE - use_power_cost = DEFAULT_CHARGE_DRAIN * 4 + idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + use_power_cost = DEFAULT_CHARGE_DRAIN * 3 complexity = 2 incompatible_modules = list(/obj/item/mod/module/active_sonar) cooldown_time = 15 SECONDS + /// Time between us displaying radial scans + var/scan_cooldown_time = 0.5 SECONDS + /// The current slice we're going to scan + var/scanned_slice = 1 + /// How many slices we make 360 + var/radar_slices = 8 // 45 degrees each + + /// A list of all creatures in range sorted by angle. + var/list/sorted_creatures = list() + /// A keyed list of all creatures + var/list/keyed_creatures = list() + + /// Time between us displaying radial scans + COOLDOWN_DECLARE(scan_cooldown) + +/obj/item/mod/module/active_sonar/Initialize(mapload) + . = ..() + for(var/i in 1 to radar_slices) + sorted_creatures += list(list()) + +/obj/item/mod/module/active_sonar/on_suit_activation() + RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(sort_all_creatures)) + +/obj/item/mod/module/active_sonar/on_suit_deactivation(deleting = FALSE) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) + +/// Detects all living creatures within world.view, and returns the amount. +/obj/item/mod/module/active_sonar/proc/detect_living_creatures() + var/creatures_detected = 0 + for(var/mob/living/creature in range(world.view, mod.wearer)) + if(creature == mod.wearer || creature.stat == DEAD) + continue + if(keyed_creatures[creature]) + creatures_detected++ + continue + sort_creature_angle(creature) + RegisterSignal(creature, COMSIG_MOVABLE_MOVED, PROC_REF(sort_creature_angle)) + creatures_detected++ + return creatures_detected + +/// Swaps around where a creature is, when they move or when they're first detected +/obj/item/mod/module/active_sonar/proc/sort_creature_angle(mob/living/creature, atom/old_loc, movement_dir, forced) + SIGNAL_HANDLER + var/oldgroup = keyed_creatures[creature] + var/newgroup = round(get_angle(mod.wearer, creature) / (360 / radar_slices)) + 1 + if(oldgroup) + if(creature.stat == DEAD || get_dist(get_turf(mod.wearer), get_turf(creature)) > world.view) + sorted_creatures[oldgroup] -= creature + keyed_creatures -= creature + UnregisterSignal(creature, COMSIG_MOVABLE_MOVED) + return + + if(oldgroup != newgroup) + sorted_creatures[oldgroup] -= creature + + sorted_creatures[newgroup] += creature + keyed_creatures[creature] = newgroup + +/// Swaps all creatures when mod.wearer moves +/obj/item/mod/module/active_sonar/proc/sort_all_creatures(mob/living/wearer, atom/old_loc, movement_dir, forced) + SIGNAL_HANDLER + + for(var/mob/living/creature as anything in keyed_creatures) + sort_creature_angle(creature) // Kinda spaghetti but it honestly seems like the shortest path to the same result + +/obj/item/mod/module/active_sonar/on_process(seconds_per_tick) + . = ..() + if(!.) + return + if(!COOLDOWN_FINISHED(src, cooldown_timer) || !COOLDOWN_FINISHED(src, scan_cooldown)) + return + detect_living_creatures() + for(var/mob/living/creature as anything in sorted_creatures[scanned_slice]) + new /obj/effect/temp_visual/sonar_ping(mod.wearer.loc, mod.wearer, creature, "sonar_ping_small", FALSE) + // Next slice! + scanned_slice++ + // IT'S ENOUGH SLICES + if(scanned_slice > radar_slices) + scanned_slice = 1 + COOLDOWN_START(src, scan_cooldown, scan_cooldown_time) /obj/item/mod/module/active_sonar/on_use() . = ..() @@ -377,14 +459,10 @@ playsound(mod.wearer, 'sound/mecha/skyfall_power_up.ogg', vol = 20, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) if(!do_after(mod.wearer, 1.1 SECONDS, target = mod)) return - var/creatures_detected = 0 - for(var/mob/living/creature in range(9, mod.wearer)) - if(creature == mod.wearer || creature.stat == DEAD) - continue + playsound(mod.wearer, 'sound/effects/ping_hit.ogg', vol = 75, vary = TRUE) // Should be audible for the radius of the sonar + to_chat(mod.wearer, span_notice("You slam your fist into the ground, sending out a sonic wave that detects [detect_living_creatures()] living beings nearby!")) + for(var/mob/living/creature as anything in keyed_creatures) new /obj/effect/temp_visual/sonar_ping(mod.wearer.loc, mod.wearer, creature) - creatures_detected++ - playsound(mod.wearer, 'sound/effects/ping_hit.ogg', vol = 75, vary = TRUE, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE) // Should be audible for the radius of the sonar - to_chat(mod.wearer, span_notice("You slam your fist into the ground, sending out a sonic wave that detects [creatures_detected] living beings nearby!")) #define SHOOTING_ASSISTANT_OFF "Currently Off" #define STORMTROOPER_MODE "Quick Fire Stormtrooper" diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index d59f065da28b9847a043bf70c80c163897ab4c0a..b01986a9522d0e3a8b1945702df92964db98b99e 100644 GIT binary patch delta 10567 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%0Ae2xh#$xnbwikr}xkN^kDcnp(ph%vkQ&S0|#_>Xh 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