diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index 35084b51aca..e07fe65552d 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -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 diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 4d0b715d762..02e133b5718 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -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 diff --git a/code/game/objects/effects/anomalies/anomalies_vortex.dm b/code/game/objects/effects/anomalies/anomalies_vortex.dm index 4c3a63ae5a6..1bb8f19b0b8 100644 --- a/code/game/objects/effects/anomalies/anomalies_vortex.dm +++ b/code/game/objects/effects/anomalies/anomalies_vortex.dm @@ -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 - diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 55239756339..ec8c398f335 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -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" diff --git a/code/game/objects/items/devices/swapper.dm b/code/game/objects/items/devices/swapper.dm index 2b6d7b5b651..a6ddb03dc0f 100644 --- a/code/game/objects/items/devices/swapper.dm +++ b/code/game/objects/items/devices/swapper.dm @@ -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() diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 4a8e1b9dbc0..f639d29e79f 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -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() diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index 26674e5d2ce..2e1d59cd4be 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -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)