mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-30 16:47:13 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user