Jury-Rigged Anomaly Tech: Adds effects to pulsed anomaly cores (#95049)

## About The Pull Request

Each type of anomaly core has an effect when pulsed by an assembly,
button, or wire. Most of these effects are a weaker version of their
respective reactive armor's unique effect (except for pyro and vortex
anomalies, which make stealth armor solely because there isn't specific
reactive armor for them).

In particular:
- Pyro anomalies make a 3x3 area of hotspots around themselves.
- Gravity anomalies gently pull most unanchored objects in a 2 tile
radius.
- Flux anomalies make a short range 10 kJ tesla zap that usually doesn't
propagate more than a single time.
- Bluespace anomalies teleport the object they are connected to up to 4
tiles away. They will teleport out of storage, and they will teleport
out of your hands or equipment slots unless they have NODROP. This has a
15 second cooldown, which is longer than reactive teleport armor.
- Vortex anomalies do a weaker version of the the vortex thing, but
limited to a 1 tile radius around the core. This has a 5 second
cooldown, and can potentially destroy the core or what it's attached to.
- Bioscrambler anomalies perform a bioscramble pulse in a 1 tile range.
This has a 10 second cooldown.
- Hallucination anomalies perform a hallucination pulse in a 1 tile
range, adding 20 seconds of hallucinations, up to a maximum of 1 minute.
This has a 10 second cooldown, so hallucinations from a single pulsed
core can stack if you stay in range.
- Dimensional anomalies perform a dimensional shift in the same range as
the science relic. This has a fixed 15 second cooldown.
- Ectoplasmic anomalies haunt a few objects in a 5x5 area around the
core for 30 seconds. This has a 60 second cooldown.
- Weather anomalies cause a single lightning bolt to strike a random
open turf in a 5x5 area around the core. This bolt deals less damage
than the reactive weather armor's bolts, and does not have an AOE.

## Why It's Good For The Game

Gives both the station and antagonists additional uses for anomaly
cores, beyond the ones that are specific items printed by science. As
some examples:
- A bundle of wired flux anomalies surrounded by tesla coils can provide
a decent drip-feed of free power to the station - only on the level of
several pacmans, but better than nothing.
- Bluespace anomalies give you a manual version of the reactive teleport
armor's teleport, but you'll need to get creative in order to actually
teleport yourself and not just what you attached the core to.
- People who really want to play the bioscrambler lottery can keep doing
it after the original anomaly is neutralized.

## Changelog

🆑
add: Anomaly cores now have effects when pulsed by an assembly, button,
or wire. These effects are generally weaker versions of the effects of
the source anomalies, or of their respective reactive armors, and many
have a 50% longer cooldown than said armors.
/🆑
This commit is contained in:
Y0SH1M4S73R
2026-02-15 17:43:45 -08:00
committed by GitHub
parent 16abd3c26c
commit bff56bf388
7 changed files with 193 additions and 29 deletions
+16
View File
@@ -128,6 +128,22 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
/// Anything else
#define TELEPORT_CHANNEL_FREE "free"
// Container flags for get_teleportable_container
/// Count mob inventory as a valid container
#define TELEPORT_CONTAINER_INCLUDE_INVENTORY (1<<0)
/// Count modsuits with at least one sealed part as valid containers, even if mob inventory is not a valid container
#define TELEPORT_CONTAINER_INCLUDE_SEALED_MODSUIT (1<<1)
/// Count atom storage as a valid container
#define TELEPORT_CONTAINER_INCLUDE_STORAGE (1<<2)
/// Count closets as a valid container
#define TELEPORT_CONTAINER_INCLUDE_CLOSET (1<<3)
/// Count vehicles as a valid container
#define TELEPORT_CONTAINER_INCLUDE_VEHICLE (1<<4)
/// Count mech equipment (particularly cargo holds and sleepers) as a valid container
#define TELEPORT_CONTAINER_INCLUDE_MECH_EQUIPMENT (1<<5)
/// Count stomachs as a valid container (ew)
#define TELEPORT_CONTAINER_INCLUDE_STOMACH (1<<6)
///Return values for moveloop Move()
#define MOVELOOP_FAILURE 0
#define MOVELOOP_SUCCESS 1
+47
View File
@@ -267,3 +267,50 @@
return FALSE
return TRUE
//Gets the topmost teleportable container
/proc/get_teleportable_container(atom/movable/teleportable, container_flags = ALL)
while(ismovable(teleportable.loc))
if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_STORAGE) && isitem(teleportable))
var/obj/item/item = teleportable
if(item.item_flags & IN_STORAGE)
break
var/atom/movable/movable = teleportable.loc
if(movable.anchored)
break
if(isliving(movable))
var/mob/living/living = movable
if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_INVENTORY))
var/list/equipped = living.get_equipped_items(INCLUDE_HELD|INCLUDE_POCKETS)
if((teleportable in equipped) && !HAS_TRAIT(teleportable, TRAIT_NODROP))
if(istype(teleportable, /obj/item/mod/control) && (container_flags & TELEPORT_CONTAINER_INCLUDE_SEALED_MODSUIT))
var/obj/item/mod/control/modsuit = teleportable
var/sealed = TRUE
for(var/datum/mod_part/part as anything in modsuit.get_part_datums(TRUE))
if((part.part_item == modsuit || part.part_item.loc != modsuit) && !part.sealed)
sealed = FALSE
break
if(!sealed)
break
else
break
if(living.buckled)
if(living.buckled.anchored)
break
else
var/obj/buckled_obj = living.buckled
buckled_obj.unbuckle_mob(living)
if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_CLOSET) && iscloset(movable))
break
if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_MECH_EQUIPMENT) && istype(movable, /obj/item/mecha_parts/mecha_equipment))
break
if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_VEHICLE) && isvehicle(movable))
var/obj/vehicle/vehicle = movable
if(vehicle.is_occupant(teleportable))
break
if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_STOMACH) && istype(movable, /obj/item/organ/stomach))
var/obj/item/organ/stomach/stomach = movable
if(teleportable in stomach.stomach_contents)
break
teleportable = movable
return teleportable
@@ -64,11 +64,6 @@
SSexplosions.lowturf += T
/obj/effect/anomaly/bhole/detonate()
new /obj/effect/temp_visual/circle_wave/vortex(get_turf(src))
new /obj/effect/temp_visual/circle_wave/vortex/small(get_turf(src))
playsound(src, 'sound/effects/hallucinations/far_noise.ogg', vol = 50)
/obj/effect/temp_visual/circle_wave/vortex
color = COLOR_BLACK
duration = 3 SECONDS
amount_to_scale = 4
@@ -808,6 +808,14 @@
/obj/effect/temp_visual/circle_wave/star_blast
color = COLOR_VOID_PURPLE
/obj/effect/temp_visual/circle_wave/vortex
color = COLOR_BLACK
duration = 3 SECONDS
amount_to_scale = 4
/obj/effect/temp_visual/circle_wave/vortex/small
amount_to_scale = 2
/obj/effect/temp_visual/focus_ring
randomdir = FALSE
name = "ring"
+2 -20
View File
@@ -78,30 +78,12 @@
update_appearance()
return CLICK_ACTION_SUCCESS
//Gets the topmost teleportable container
/obj/item/swapper/proc/get_teleportable_container()
var/atom/movable/teleportable = src
while(ismovable(teleportable.loc))
var/atom/movable/AM = teleportable.loc
if(AM.anchored)
break
if(isliving(AM))
var/mob/living/L = AM
if(L.buckled)
if(L.buckled.anchored)
break
else
var/obj/buckled_obj = L.buckled
buckled_obj.unbuckle_mob(L)
teleportable = AM
return teleportable
/obj/item/swapper/proc/swap(mob/user)
if(QDELETED(linked_swapper) || isnull(linked_swapper.loc) || world.time < linked_swapper.cooldown)
return
var/atom/movable/A = get_teleportable_container()
var/atom/movable/B = linked_swapper.get_teleportable_container()
var/atom/movable/A = get_teleportable_container(src)
var/atom/movable/B = get_teleportable_container(linked_swapper)
var/target_A = A.drop_location()
var/target_B = B.drop_location()
+5 -3
View File
@@ -25,7 +25,9 @@
var/obj/item/assembly_holder/holder = null
var/assembly_behavior = ASSEMBLY_FUNCTIONAL_OUTPUT // how does the assembly behave with respect to what it's connected to
var/datum/wires/connected = null
var/next_activate = 0 //When we're next allowed to activate - for spam control
COOLDOWN_DECLARE(next_activate)
/// Length of the cooldown between activations
var/activation_cooldown = 3 SECONDS
/obj/item/assembly/Destroy()
holder = null
@@ -99,9 +101,9 @@
/// What the device does when turned on
/obj/item/assembly/proc/activate(mob/activator)
if(QDELETED(src) || !secured || (next_activate > world.time))
if(QDELETED(src) || !secured || !COOLDOWN_FINISHED(src, next_activate))
return FALSE
next_activate = world.time + 30
COOLDOWN_START(src, next_activate, activation_cooldown)
return TRUE
/obj/item/assembly/proc/toggle_secure()
@@ -8,6 +8,7 @@
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
resistance_flags = FIRE_PROOF
custom_materials = null
assembly_behavior = ASSEMBLY_FUNCTIONAL_OUTPUT
var/anomaly_type = /obj/effect/anomaly
/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
@@ -42,47 +43,131 @@
icon_state = "pyro_core"
anomaly_type = /obj/effect/anomaly/pyro
/obj/item/assembly/signaler/anomaly/pyro/signal()
var/turf/our_turf = get_turf(src)
if(!our_turf)
return
playsound(our_turf, 'sound/effects/magic/fireball.ogg', 100, TRUE)
for(var/turf/turf as anything in RANGE_TURFS(1, our_turf))
new /obj/effect/hotspot(turf)
/obj/item/assembly/signaler/anomaly/grav
name = "\improper gravitational anomaly core"
desc = "The neutralized core of a gravitational anomaly. It feels much heavier than it looks. It'd probably be valuable for research."
icon_state = "grav_core"
anomaly_type = /obj/effect/anomaly/grav
/obj/item/assembly/signaler/anomaly/grav/signal()
for(var/obj/object in orange(2, src))
if(!object.anchored)
step_towards(object,src)
for(var/mob/living/living in orange(2, src))
if(!living.mob_negates_gravity())
step_towards(living,src)
/obj/item/assembly/signaler/anomaly/flux
name = "\improper flux anomaly core"
desc = "The neutralized core of a flux anomaly. Touching it makes your skin tingle. It'd probably be valuable for research."
icon_state = "flux_core"
anomaly_type = /obj/effect/anomaly/flux
/obj/item/assembly/signaler/anomaly/flux/signal()
tesla_zap(src, 0, 10 KILO JOULES, 5 KILO JOULES, ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_GENERATES_POWER)
/obj/item/assembly/signaler/anomaly/bluespace
name = "\improper bluespace anomaly core"
desc = "The neutralized core of a bluespace anomaly. It keeps phasing in and out of view. It'd probably be valuable for research."
icon_state = "anomaly_core"
anomaly_type = /obj/effect/anomaly/bluespace
activation_cooldown = 15 SECONDS // Slightly longer than reactive teleport armor cooldown
/obj/item/assembly/signaler/anomaly/bluespace/signal()
var/atom/movable/to_teleport = get_teleportable_container(src, container_flags = TELEPORT_CONTAINER_INCLUDE_SEALED_MODSUIT)
if(!to_teleport)
return
var/turf/teleportable_turf = get_turf(to_teleport)
playsound(teleportable_turf, 'sound/effects/magic/blink.ogg', 100, TRUE)
do_teleport(to_teleport, teleportable_turf, 4, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/item/assembly/signaler/anomaly/vortex
name = "\improper vortex anomaly core"
desc = "The neutralized core of a vortex anomaly. It won't sit still, as if some invisible force is acting on it. It'd probably be valuable for research."
icon_state = "vortex_core"
anomaly_type = /obj/effect/anomaly/bhole
activation_cooldown = 5 SECONDS
var/turf_contents_pull_chance = 33
var/turf_destroy_chance = 25
var/max_explosion_force = EXPLODE_HEAVY
// Causes a small vortex pulse
/obj/item/assembly/signaler/anomaly/vortex/signal()
var/turf/our_turf = get_turf(src)
if(!our_turf)
return
playsound(our_turf, 'sound/effects/bamf.ogg', 100, TRUE)
for(var/turf/turf as anything in RANGE_TURFS(1, our_turf))
var/explosion_power = rand(EXPLODE_NONE, max_explosion_force)
if(prob(turf_contents_pull_chance))
for(var/obj/object in turf.contents)
if(object.anchored)
switch(explosion_power)
if(EXPLODE_DEVASTATE)
SSexplosions.high_mov_atom += object
if(EXPLODE_HEAVY)
SSexplosions.med_mov_atom += object
if(EXPLODE_LIGHT)
SSexplosions.low_mov_atom += object
else
step_towards(object, src)
for(var/mob/living/living in turf.contents)
step_towards(living, src)
if(prob(turf_destroy_chance))
switch(explosion_power)
if(EXPLODE_DEVASTATE)
SSexplosions.highturf += turf
if(EXPLODE_HEAVY)
SSexplosions.medturf += turf
if(EXPLODE_LIGHT)
SSexplosions.lowturf += turf
new /obj/effect/temp_visual/circle_wave/vortex(our_turf)
/obj/item/assembly/signaler/anomaly/bioscrambler
name = "\improper bioscrambler anomaly core"
desc = "The neutralized core of a bioscrambler anomaly. It's squirming, as if moving. It'd probably be valuable for research."
icon_state = "bioscrambler_core"
anomaly_type = /obj/effect/anomaly/bioscrambler
activation_cooldown = 10 SECONDS
/obj/item/assembly/signaler/anomaly/bioscrambler/signal()
new /obj/effect/temp_visual/circle_wave/bioscrambler(get_turf(src))
for(var/mob/living/carbon/nearby in hearers(1, src))
nearby.bioscramble(name)
/obj/item/assembly/signaler/anomaly/hallucination
name = "\improper hallucination anomaly core"
desc = "The neutralized core of a hallucination anomaly. It seems to be moving, but it's probably your imagination. It'd probably be valuable for research."
icon_state = "hallucination_core"
anomaly_type = /obj/effect/anomaly/hallucination
activation_cooldown = 10 SECONDS
/obj/item/assembly/signaler/anomaly/hallucination/signal()
visible_hallucination_pulse(get_turf(src), 2, 20 SECONDS, 1 MINUTES)
/obj/item/assembly/signaler/anomaly/dimensional
name = "\improper dimensional anomaly core"
desc = "The neutralized core of a dimensional anomaly. Objects reflected on its surface don't look quite right. It'd probably be valuable for research."
icon_state = "dimensional_core"
anomaly_type = /obj/effect/anomaly/dimensional
activation_cooldown = 15 SECONDS // A bit longer than reactive barricade armor cooldown
/obj/item/assembly/signaler/anomaly/dimensional/signal()
var/turf/our_turf = get_turf(src)
if(!our_turf)
return
var/new_theme_path = pick(subtypesof(/datum/dimension_theme))
var/datum/dimension_theme/theme = SSmaterials.dimensional_themes[new_theme_path]
for(var/turf/turf as anything in RANGE_TURFS(1, our_turf))
theme.apply_theme(turf, show_effect = TRUE)
/obj/item/assembly/signaler/anomaly/dimensional/Initialize(mapload)
. = ..()
@@ -94,6 +179,10 @@
desc = "The neutralized core of an ectoplasmic anomaly. When you hold it close, you can hear faint murmuring from inside. It'd probably be valuable for research."
icon_state = "dimensional_core"
anomaly_type = /obj/effect/anomaly/ectoplasm
activation_cooldown = 60 SECONDS // A bit longer than reactive posession armor cooldown
/obj/item/assembly/signaler/anomaly/ectoplasm/signal()
haunt_outburst(get_turf(src), 2, 33, 30 SECONDS)
/obj/item/assembly/signaler/anomaly/weather
name = "\improper weather anomaly core"
@@ -103,3 +192,28 @@
/// Used in weather towers to track uses before depleting
var/charges = 8
/obj/item/assembly/signaler/anomaly/weather/signal()
var/turf/our_turf = get_turf(src)
if(!our_turf)
return
var/list/possible_targets = list()
for(var/turf/turf as anything in RANGE_TURFS(2, our_turf))
if(isopenturf(turf))
possible_targets += turf
if(!length(possible_targets))
return
var/turf/target = pick(possible_targets)
new /obj/effect/temp_visual/telegraphing/thunderbolt(target)
addtimer(CALLBACK(src, PROC_REF(strike), target), 1 SECONDS)
/obj/item/assembly/signaler/anomaly/weather/proc/strike(turf/target)
playsound(target, 'sound/effects/magic/lightningbolt.ogg', 66, TRUE)
new /obj/effect/temp_visual/thunderbolt(target)
for(var/mob/living/hit_mob in target)
to_chat(hit_mob, span_userdanger("You've been struck by lightning!"))
hit_mob.electrocute_act(20, src, flags = SHOCK_TESLA|SHOCK_NOSTUN)
for(var/obj/hit_thing in target)
hit_thing.take_damage(15, BURN, ENERGY, FALSE)