diff --git a/code/__defines/movement.dm b/code/__defines/movement.dm index 8de05c867cf..81cfe3f55c6 100644 --- a/code/__defines/movement.dm +++ b/code/__defines/movement.dm @@ -2,3 +2,17 @@ #define TELEPORT_CHANNEL_BLUESPACE "bluespace" /// Quantum-based teleportation, requires both sender and receiver, but is free from normal disruption #define TELEPORT_CHANNEL_QUANTUM "quantum" + +// Container flags for get_teleportable_container +/// Count mob inventory as a valid container +#define TELEPORT_CONTAINER_INCLUDE_INVENTORY (1<<0) +/// Count rigsuits with at least one sealed part as valid containers, even if mob inventory is not a valid container +#define TELEPORT_CONTAINER_INCLUDE_SEALED_RIGSUIT (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) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 0dd60192885..8119ca062dd 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -192,3 +192,46 @@ GLOBAL_LIST_INIT(bluespace_item_types, list( 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.storage_depth(item.loc)) + 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/rig) && (container_flags & TELEPORT_CONTAINER_INCLUDE_SEALED_RIGSUIT)) + var/obj/item/rig/rigsuit = teleportable + var/sealed = TRUE + for(var/obj/item/clothing/part as anything in rigsuit) + if((part == rigsuit || part.loc != rigsuit) && rigsuit.offline) + sealed = FALSE + break + if(!sealed) + break + else + break + if(living.buckled) + if(living.buckled.anchored) + break + else + var/obj/buckle_obj = living.buckled + buckle_obj.unbuckle_mob(living) + if(!(container_flags & TELEPORT_CONTAINER_INCLUDE_CLOSET) && istype(movable, /obj/structure/closet)) + 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) && istype(movable, /obj/vehicle)) + var/obj/vehicle/vehicle = movable + if(vehicle.load) + break + teleportable = movable + return teleportable diff --git a/code/datums/supplypacks/science.dm b/code/datums/supplypacks/science.dm index 1e5747bc8b8..1691fbc608e 100644 --- a/code/datums/supplypacks/science.dm +++ b/code/datums/supplypacks/science.dm @@ -159,6 +159,14 @@ /datum/supply_pack/sci/anomaly_harvesting name = "Anomaly Harvesting crate" desc = "Contains the tools to start anomaly harvesting. Requires Research access." - cost = 300 + cost = 350 containertype = /obj/structure/largecrate/anomaly access = ACCESS_RESEARCH + +/datum/supply_pack/sci/latent_anomaly + name = "Latent Anomaly crate" + desc = "Contains a latent anomaly core." + cost = 300 + contains = list(/obj/item/assembly/signaler/anomaly/choice) + containertype = /obj/structure/closet/crate/secure/science + access = ACCESS_RESEARCH diff --git a/code/game/objects/effects/anomalies/anomalies_dust.dm b/code/game/objects/effects/anomalies/anomalies_dust.dm index 5e890635825..a24626b8b41 100644 --- a/code/game/objects/effects/anomalies/anomalies_dust.dm +++ b/code/game/objects/effects/anomalies/anomalies_dust.dm @@ -54,9 +54,7 @@ if(prob(15)) person.Stun(2) to_chat(person, span_danger(pick("You have a coughing fit!", "You can't stop coughing!"))) - addtimer(CALLBACK(person, TYPE_PROC_REF(/mob, emote), "cough"), 1 SECONDS) - addtimer(CALLBACK(person, TYPE_PROC_REF(/mob, emote), "cough"), 3 SECONDS) - addtimer(CALLBACK(person, TYPE_PROC_REF(/mob, emote), "cough"), 6 SECONDS) + addtimer(CALLBACK(src, PROC_REF(extraCough), person), 3 SECONDS) for(var/turf/simulated/floor/ground in circleviewturfs(src, 3)) if(ground.can_dirty) @@ -69,6 +67,10 @@ if(prob(2)) new /obj/effect/decal/cleanable/filth(ground) +/obj/effect/anomaly/dust/proc/extraCough(mob/living/coughing) + coughing.emote("cough") + addtimer(CALLBACK(coughing, TYPE_PROC_REF(/mob, emote), "cough"), 3 SECONDS) + /obj/effect/anomaly/dust/detonate() COOLDOWN_RESET(src, pulse_cooldown) anomalyEffect() diff --git a/code/game/objects/effects/anomalies/anomalies_weather.dm b/code/game/objects/effects/anomalies/anomalies_weather.dm index ca21a910c3f..03367a3f3fa 100644 --- a/code/game/objects/effects/anomalies/anomalies_weather.dm +++ b/code/game/objects/effects/anomalies/anomalies_weather.dm @@ -171,9 +171,10 @@ /obj/effect/anomaly/weather/rain selected_weather = /datum/anomalous_weather/rain +/* /obj/effect/anomaly/weather/acidrain selected_weather = /datum/anomalous_weather/rain/acid - +*/ /obj/effect/anomaly/weather/storm selected_weather = /datum/anomalous_weather/rain/storm diff --git a/code/game/objects/effects/anomalies/anomalies_weather_themes.dm b/code/game/objects/effects/anomalies/anomalies_weather_themes.dm index 8290f6f11fc..aab5787dd7e 100644 --- a/code/game/objects/effects/anomalies/anomalies_weather_themes.dm +++ b/code/game/objects/effects/anomalies/anomalies_weather_themes.dm @@ -157,12 +157,12 @@ T.wet_floor(1) if(prob(0.025) && !isopenturf(T)) lightning_strike(T) - +/* /datum/anomalous_weather/rain/acid name = "Acid Rain" reagent_id = REAGENT_ID_SACID telegraph_message = "Strange clouds begin to cover the ceiling, an acidid tinge on them..." - +*/ /datum/anomalous_weather/hail name = "Hail" icon_state = "snowfall_heavy_old" diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 598e2119a32..c5c885dbb73 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -27,16 +27,12 @@ ///var used for attack_self chain var/special_handling = FALSE + COOLDOWN_DECLARE(next_activate) + var/activation_cooldown = 3 SECONDS + /obj/item/assembly/proc/holder_movement() return -/obj/item/assembly/proc/process_cooldown() - if(cooldown) - return FALSE - cooldown = TRUE - VARSET_IN(src, cooldown, FALSE, 2 SECONDS) - return TRUE - /obj/item/assembly/proc/pulsed(var/radio = 0) if(holder && (wires & WIRE_RECEIVE)) activate() @@ -52,8 +48,9 @@ return 1 /obj/item/assembly/proc/activate() - if(!secured || !process_cooldown()) + if(QDELETED(src) || !secured || !COOLDOWN_FINISHED(src, next_activate)) return FALSE + COOLDOWN_START(src, next_activate, activation_cooldown) return TRUE /obj/item/assembly/proc/toggle_secure() diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index 034d4a179db..de2a999a4a8 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -97,7 +97,7 @@ return TRUE /obj/item/assembly/infra/proc/trigger_beam() - if(!process_cooldown()) + if(!COOLDOWN_FINISHED(src, next_activate)) return FALSE pulse(0) QDEL_LIST_NULL(i_beams) //They will get recreated next process() if the situation is still appropriate diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index ae3cfbccad4..413663a53b6 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -45,7 +45,7 @@ sense() /obj/item/assembly/prox_sensor/proc/sense() - if((!holder && !secured) || !scanning || !process_cooldown()) + if((!holder && !secured) || !scanning || !COOLDOWN_FINISHED(src, next_activate)) return FALSE var/turf/mainloc = get_turf(src) pulse(0) diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index 328ffb83601..3c8b4c3e994 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -22,7 +22,7 @@ set_frequency(frequency) /obj/item/assembly/signaler/activate() - if(!process_cooldown()) + if(!COOLDOWN_FINISHED(src, next_activate)) return FALSE signal() return TRUE @@ -82,6 +82,8 @@ ..() /obj/item/assembly/signaler/proc/signal() + if(!COOLDOWN_FINISHED(src, next_activate)) + return FALSE if(!radio_connection) return if(is_jammed(src)) @@ -92,6 +94,7 @@ signal.encryption = code signal.data["message"] = "ACTIVATE" radio_connection.post_signal(src, signal) + COOLDOWN_START(src, next_activate, activation_cooldown) /obj/item/assembly/signaler/pulse(var/radio = 0) if(is_jammed(src)) diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index 1d7d4ee7363..ee55878dfef 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -17,9 +17,11 @@ return FALSE if(signal.encryption != code) return FALSE - for(var/obj/effect/anomaly/anomaly in get_turf(src)) + if(istype(loc, /obj/effect/anomaly)) + var/obj/effect/anomaly/anomaly = loc anomaly.anomalyNeutralize() - return TRUE + return TRUE + return FALSE /obj/item/assembly/signaler/anomaly/attack_self(mob/user) . = ..(user) @@ -63,11 +65,39 @@ icon_state = "flux_core" anomaly_type = /obj/effect/anomaly/flux +/obj/item/assembly/signaler/anomaly/flux/receive_signal() + if(..()) + return + if(ishuman(loc)) + var/mob/living/carbon/human/human = loc + human.electrocute_act(5, src) + return + + var/source = null + if(connected) + var/datum/wires/wires = connected + source = wires.holder + tesla_zap(source ? source : src, 2, 1000, FALSE, TRUE, 1) + /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 + +/obj/item/assembly/signaler/anomaly/bluespace/receive_signal() + if(..()) + return + var/atom/movable/to_teleport = get_teleportable_container(src, container_flags = TELEPORT_CONTAINER_INCLUDE_SEALED_RIGSUIT|TELEPORT_CONTAINER_INCLUDE_CLOSET) + if(!to_teleport) + return + if(isliving(to_teleport.loc)) + var/mob/living/creature = to_teleport.loc + creature.drop_from_inventory(to_teleport, get_turf(creature)) + var/turf/teleportable_turf = get_turf(to_teleport) + playsound(teleportable_turf, 'sound/effects/phasein.ogg', 100, TRUE) + do_teleport(to_teleport, teleportable_turf, 4, channel = TELEPORT_CHANNEL_BLUESPACE) /obj/item/assembly/signaler/anomaly/grav name = "\improper gravitational anomaly core" @@ -75,23 +105,68 @@ icon_state = "grav_core" anomaly_type = /obj/effect/anomaly/grav +/obj/item/assembly/signaler/anomaly/grav/receive_signal() + if(..()) + return + for(var/obj/object in orange(2, get_turf(src))) + if(!object.anchored) + step_towards(object, src) + for(var/mob/living/living in orange(2, get_turf(src))) + if(!living.can_overcome_gravity()) + step_towards(living, src) + /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 + +/obj/item/assembly/signaler/anomaly/dimensional/receive_signal() + if(..()) + return + var/turf/our_turf = get_turf(src) + if(!our_turf) + return + var/theme_path = pick(subtypesof(/datum/dimension_theme)) + var/datum/dimension_theme/theme = 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/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/receive_signal() + if(..()) + return + new /obj/effect/temp_visual/circle_wave/bioscrambler(get_turf(src)) + for(var/mob/living/carbon/nearby in hearers(1, get_turf(src))) + randmutb(nearby) + domutcheck(nearby, null) /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/receive_signal() + if(..()) + return + for(var/mob/living/nearby_living in view(get_turf(src), 2)) + if(HAS_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE)) + continue + + if(nearby_living.is_blind()) + continue + + nearby_living.hallucination += 25 /obj/item/assembly/signaler/anomaly/pyro name = "\improper pyroclastic anomaly core" @@ -99,14 +174,63 @@ icon_state = "pyro_core" anomaly_type = /obj/effect/anomaly/pyro +/obj/item/assembly/signaler/anomaly/pyro/receive_signal() + if(..()) + return + var/turf/our_turf = get_turf(src) + if(!our_turf) + return + playsound(our_turf, 'sound/magic/fireball.ogg', 100, TRUE) + for(var/turf/turf as anything in RANGE_TURFS(1, our_turf)) + our_turf.assume_gas(GAS_PHORON, 10, T20C) + our_turf.hotspot_expose(700, 400) + /obj/item/assembly/signaler/anomaly/weather name = "\improper weather anomaly core" desc = "The neutralized core of a weather anomaly. The sound of thunder can be heard in the distance. It'd probably be valuable for research." icon_state = "weather_core" anomaly_type = /obj/effect/anomaly/weather +/obj/item/assembly/signaler/anomaly/weather/receive_signal() + if(..()) + return + 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)) + possible_targets += turf + if(!length(possible_targets)) + return + var/turf/target = pick(possible_targets) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(lightning_strike), target), 1 SECONDS) + /obj/item/assembly/signaler/anomaly/dust name = "\improper dust anomaly core" desc = "The neutralized core of a dust anomaly. It seems to leave some dirt on touch. It'd probably be valuable for research." icon_state = "dust_core" anomaly_type = /obj/effect/anomaly/dust + +/obj/item/assembly/signaler/anomaly/dust/receive_signal() + if(..()) + return + new /obj/effect/temp_visual/circle_wave/dirt(get_turf(src)) + for(var/mob/living/carbon/human/person in view(get_turf(src), 2)) + person.germ_level += rand(5, 10) + if(person.isSynthetic()) + continue + if(person.is_mouth_covered()) + continue + if(!person.has_lungs()) + continue + person.emote(prob(50) ? "cough" : "sneeze") + person.Weaken(2) + person.adjustOxyLoss(10) + if(prob(15)) + person.Stun(2) + to_chat(person, span_danger(pick("You have a coughing fit!", "You can't stop coughing!"))) + addtimer(CALLBACK(src, PROC_REF(extraCough), person), 3 SECONDS) + +/obj/item/assembly/signaler/anomaly/dust/proc/extraCough(mob/living/coughing) + coughing.emote("cough") + addtimer(CALLBACK(coughing, TYPE_PROC_REF(/mob, emote), "cough"), 3 SECONDS)