diff --git a/code/__defines/anomaly.dm b/code/__defines/anomaly.dm new file mode 100644 index 0000000000..dfb594e680 --- /dev/null +++ b/code/__defines/anomaly.dm @@ -0,0 +1,47 @@ +/** + * # Anomaly Defines + * This file contains defines for the random event anomaly subtypes. + */ + +///Time in ticks before the anomaly goes poof/explodes depending on type. +#define ANOMALY_COUNTDOWN_TIMER (120 SECONDS) + +/** + * Nuisance/funny anomalies + */ + +///Time in seconds before anomaly spawns +#define ANOMALY_START_MEDIUM_TIME (6 EVENT_SECONDS) +///Time in seconds before anomaly is announced +#define ANOMALY_ANNOUNCE_MEDIUM_TIME (2 EVENT_SECONDS) +///Let them know how far away the anomaly is +#define ANOMALY_ANNOUNCE_MEDIUM_TEXT "long range scanners. Expected location:" + +/** + * Chaotic but not harmful anomalies. Give the station a chance to find it on their own. + */ + +///Time in seconds before anomaly spawns +#define ANOMALY_START_HARMFUL_TIME (2 EVENT_SECONDS) +///Time in seconds before anomaly is announced +#define ANOMALY_ANNOUNCE_HARMFUL_TIME (30 EVENT_SECONDS) +///Let them know how far away the anomaly is +#define ANOMALY_ANNOUNCE_HARMFUL_TEXT "localized scanners. Detected location:" + +/** + * Anomalies that can fuck you up. Give them a bit of warning. + */ + +///Time in seconds before anomaly spawns +#define ANOMALY_START_DANGEROUS_TIME (2 EVENT_SECONDS) +///Time in seconds before anomaly is announced +#define ANOMALY_ANNOUNCE_DANGEROUS_TIME (30 EVENT_SECONDS) +///Let them know how far away the anomaly is +#define ANOMALY_ANNOUNCE_DANGEROUS_TEXT "localized scanners. Detected location:" + +#define GRAVITATIONAL_ANOMALY "gravitational_anomaly" +#define FLUX_ANOMALY "flux_anomaly" +#define PYRO_ANOMALY "pyro_anomaly" +#define BIOSCRAMBLER_ANOMALY "bioscrambler_anomaly" +#define HALLUCINATION_ANOMALY "hallucination_anomaly" +#define DIMENSIONAL_ANOMALY "dimensional_anomaly" diff --git a/code/__defines/color.dm b/code/__defines/color.dm index 1e4ef589e8..7c3d32dca6 100644 --- a/code/__defines/color.dm +++ b/code/__defines/color.dm @@ -96,6 +96,8 @@ #define COLOR_NUKIES_YELLOW "#ffe900" #define COLOR_DESATTI_PRPLOW "#3300cc" #define COLOR_DESATTI_PRPHI "#6600cc" +#define COLOR_FULL_TONER_BLACK "#101010" +#define COLOR_VOID_PURPLE "#53277E" #define PIPE_COLOR_GREY "#808080" #define PIPE_COLOR_RED "#ff0000" diff --git a/code/__defines/is_helpers.dm b/code/__defines/is_helpers.dm index a9873595e9..fde9c8da30 100644 --- a/code/__defines/is_helpers.dm +++ b/code/__defines/is_helpers.dm @@ -97,3 +97,5 @@ GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur"))) #define ismopable(A) (A && (A.plane <= OBJ_PLANE)) //If something can be cleaned by floor-cleaning devices such as mops or clean bots #define isfloorturf(A) (istype(A, /turf/simulated/floor)) + +#define iseffect(O) (istype(O, /obj/effect)) diff --git a/code/__defines/math.dm b/code/__defines/math.dm index 82dd3b046c..555cd94d2e 100644 --- a/code/__defines/math.dm +++ b/code/__defines/math.dm @@ -237,3 +237,10 @@ return 1 else return 0 + +/// Converts a probability/second chance to probability/seconds_per_tick chance +/// For example, if you want an event to happen with a 10% per second chance, but your proc only runs every 5 seconds, do `if(prob(100*SPT_PROB_RATE(0.1, 5)))` +#define SPT_PROB_RATE(prob_per_second, seconds_per_tick) (1 - (1 - (prob_per_second)) ** (seconds_per_tick)) + +/// Like SPT_PROB_RATE but easier to use, simply put `if(SPT_PROB(10, 5))` +#define SPT_PROB(prob_per_second_percent, seconds_per_tick) (prob(100*SPT_PROB_RATE((prob_per_second_percent)/100, (seconds_per_tick)))) diff --git a/code/__defines/research/anomalies.dm b/code/__defines/research/anomalies.dm new file mode 100644 index 0000000000..71f4842f0c --- /dev/null +++ b/code/__defines/research/anomalies.dm @@ -0,0 +1,7 @@ +///Defines for the different types of explosion a flux anomaly can have +#define FLUX_NO_EMP 0 +#define FLUX_EMP 1 +#define FLUX_LIGHT_EMP 2 + +/// Chance of anomalies moving every process tick +#define ANOMALY_MOVECHANCE 45 diff --git a/code/__defines/traits/declarations.dm b/code/__defines/traits/declarations.dm index afd94c431d..674ba25e31 100644 --- a/code/__defines/traits/declarations.dm +++ b/code/__defines/traits/declarations.dm @@ -45,3 +45,5 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_TESLA_SHOCKIMMUNE "tesla_shock_immunity" /// Do we show up as a changeling / the wrong body-mind pair to sleevmates #define UNIQUE_MINDSTRUCTURE "unique_mindstructure" +// Owner is immune to hallucinations +#define TRAIT_MADNESS_IMMUNE "supermatter_madness_immune" diff --git a/code/_global_vars/traits/_traits.dm b/code/_global_vars/traits/_traits.dm index 8e4be2f439..f99323c8d6 100644 --- a/code/_global_vars/traits/_traits.dm +++ b/code/_global_vars/traits/_traits.dm @@ -27,6 +27,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NOFIRE_SPREAD" = TRAIT_NOFIRE_SPREAD, "TRAIT_NO_EXTINGUISH" = TRAIT_NO_EXTINGUISH, "TRAIT_TESLA_SHOCKIMMUNE" = TRAIT_TESLA_SHOCKIMMUNE, + "TRAIT_MADNESS_IMMUNE" = TRAIT_MADNESS_IMMUNE, ), /obj = list( "TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE, diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index a730d10bda..fa200afa06 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -151,9 +151,17 @@ return 0 //Checks for specific paths in a list -/proc/is_path_in_list(var/atom/A, var/list/L) +/** + * Arguments: + * A : Typepath to check + * L : A list of typepath to check A against + * zebra: Wether to use the value of the path in the list instead of just returning TRUE when a match is found + */ +/proc/is_path_in_list(var/atom/A, var/list/L, zebra = FALSE) for(var/path in L) if(ispath(A, path)) + if(ispath(A, path)) + return !zebra || L[path] return 1 return 0 @@ -1083,3 +1091,19 @@ GLOBAL_LIST_EMPTY(json_cache) for(var/i in 1 to count) retval += to_flatten[i] return retval + +/proc/pick_weight(list/list_to_pick) + var/total = 0 + var/item + for(item in list_to_pick) + if(!list_to_pick[item]) + list_to_pick[item] = 0 + total += list_to_pick[item] + + total = rand(1, total) + for(item in list_to_pick) + total -= list_to_pick[item] + if(total <= 0 && list_to_pick[item]) + return item + + return null diff --git a/code/_helpers/turfs.dm b/code/_helpers/turfs.dm index d4e1b08e6d..1bf37c6ae8 100644 --- a/code/_helpers/turfs.dm +++ b/code/_helpers/turfs.dm @@ -189,3 +189,57 @@ var/testdir = get_dir(target, origin) return (dir & testdir) return TRUE + +///similar function to RANGE_TURFS(), but will search spiralling outwards from the center (like the above, but only turfs) +/proc/spiral_range_turfs(dist = 0, center = usr, orange = FALSE, list/outlist = list(), tick_checked) + outlist.Cut() + if(!dist) + outlist += center + return outlist + + var/turf/t_center = get_turf(center) + if(!t_center) + return outlist + + var/list/turf_list = outlist + var/turf/checked_turf + var/y + var/x + var/c_dist = 1 + + if(!orange) + turf_list += t_center + + while( c_dist <= dist ) + y = t_center.y + c_dist + x = t_center.x - c_dist + 1 + for(x in x to t_center.x + c_dist) + checked_turf = locate(x, y, t_center.z) + if(checked_turf) + turf_list += checked_turf + + y = t_center.y + c_dist - 1 + x = t_center.x + c_dist + for(y in t_center.y - c_dist to y) + checked_turf = locate(x, y, t_center.z) + if(checked_turf) + turf_list += checked_turf + + y = t_center.y - c_dist + x = t_center.x + c_dist - 1 + for(x in t_center.x - c_dist to x) + checked_turf = locate(x, y, t_center.z) + if(checked_turf) + turf_list += checked_turf + + y = t_center.y - c_dist + 1 + x = t_center.x - c_dist + for(y in y to t_center.y + c_dist) + checked_turf = locate(x, y, t_center.z) + if(checked_turf) + turf_list += checked_turf + c_dist++ + if(tick_checked) + CHECK_TICK + + return turf_list diff --git a/code/controllers/subsystems/research.dm b/code/controllers/subsystems/research.dm index 9cb5b705d2..78db93a99d 100644 --- a/code/controllers/subsystems/research.dm +++ b/code/controllers/subsystems/research.dm @@ -44,7 +44,14 @@ SUBSYSTEM_DEF(research) /obj/item/research_sample/common = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS*0.5), /obj/item/research_sample/uncommon = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS*0.5), /obj/item/research_sample/rare = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS*0.5), - /obj/item/research_sample/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS*0.5) + /obj/item/research_sample/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS*0.5), + /obj/item/assembly/signaler/anomaly/bioscrambler = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/dimensional = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/flux = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/grav = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/hallucination = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/pyro = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) ) ///Allows repeated deconstruction of these items for points. These items MUST be in techweb_point_items as well. var/list/techweb_repeatable_items = list( diff --git a/code/game/gamemodes/technomancer/devices/shield_armor.dm b/code/game/gamemodes/technomancer/devices/shield_armor.dm index a740a02c7f..b5330cac8c 100644 --- a/code/game/gamemodes/technomancer/devices/shield_armor.dm +++ b/code/game/gamemodes/technomancer/devices/shield_armor.dm @@ -14,7 +14,7 @@ desc = "This armor has no inherent ability to absorb shock, as normal armor usually does. Instead, this emits a strong field \ around the wearer, designed to protect from most forms of harm, from lasers to bullets to close quarters combat. It appears to \ require a very potent supply of an energy of some kind in order to function." - icon_state = "shield_armor_0" + icon_state = "reactive" blood_overlay_type = "armor" slowdown = 0 armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0) diff --git a/code/game/objects/effects/anomalies/_anomalies.dm b/code/game/objects/effects/anomalies/_anomalies.dm new file mode 100644 index 0000000000..4d0396369f --- /dev/null +++ b/code/game/objects/effects/anomalies/_anomalies.dm @@ -0,0 +1,121 @@ +/obj/effect/anomaly + name = "anomaly" + desc = "A mysterious anomaly, seen commonly only in the region of space that the station orbits..." + icon = 'icons/effects/anomalies.dmi' + icon_state = "vortex" + density = FALSE + anchored = TRUE + light_range = 3 + + var/obj/item/assembly/signaler/anomaly/anomaly_core = /obj/item/assembly/signaler/anomaly + var/area/impact_area + + var/lifespan = ANOMALY_COUNTDOWN_TIMER + var/death_time + + var/countdown_colour + var/obj/effect/countdown/anomaly/countdown + + var/immortal = FALSE + var/move_chance = ANOMALY_MOVECHANCE + +/obj/effect/anomaly/Initialize(mapload, new_lifespan, drops_core = TRUE) + . = ..() + + START_PROCESSING(SSobj, src) + impact_area = get_area(src) + + if(!impact_area) + return INITIALIZE_HINT_QDEL + + if(!drops_core) + anomaly_core = null + + if(anomaly_core) + anomaly_core = new anomaly_core(src) + anomaly_core.code = rand(1, 100) + anomaly_core.anomaly_type = type + + anomaly_core.set_frequency(sanitize_frequency(get_rand_frequency())) + + if(new_lifespan) + lifespan = new_lifespan + death_time = world.time + lifespan + + countdown = new(src) + if(countdown_colour) + countdown_colour = countdown_colour + + if(immortal) + return + countdown.start() + +/obj/effect/anomaly/process(seconds_per_tick) + anomalyEffect(seconds_per_tick) + if(death_time < world.time && !immortal) + if(loc) + detonate() + qdel(src) + +/obj/effect/anomaly/Destroy() + STOP_PROCESSING(SSobj, src) + QDEL_NULL(countdown) + QDEL_NULL(anomaly_core) + return ..() + +/obj/effect/anomaly/proc/anomalyEffect(seconds_per_tick) + if(prob(move_chance)) + move_anomaly() + +/obj/effect/anomaly/proc/move_anomaly() + step(src, pick(GLOB.alldirs)) + +/obj/effect/anomaly/proc/detonate() + return + +/obj/effect/anomaly/ex_act(strength) + if(strength <= 1) + qdel(src) + return TRUE + return FALSE + +/obj/effect/anomaly/proc/anomalyNeutralize() + new /obj/effect/effect/smoke/bad(loc) + if(!isnull(anomaly_core)) + anomaly_core.forceMove(get_turf(src)) + anomaly_core = null + qdel(src) + +/obj/effect/anomaly/proc/stabilize(anchor = FALSE, has_core = TRUE) + immortal = TRUE + name = (has_core ? "stable " : "hollow ") + name + if(!has_core) + QDEL_NULL(anomaly_core) + if(anchor) + move_chance = 0 + +/obj/effect/anomaly/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/analyzer)) + if(anomaly_core) + to_chat(user, span_notice("Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(anomaly_core.frequency)], code [anomaly_core.code].")) + return TRUE + return ..() + +/proc/generate_anomaly(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5, has_changed_lifespan = TRUE) + var/turf/local_turf = pick(RANGE_TURFS(anomalyrange, anomalycenter)) + if(!local_turf) + return + switch(type) + if(FLUX_ANOMALY) + var/explosive = has_changed_lifespan ? FLUX_NO_EMP : FLUX_LIGHT_EMP + new /obj/effect/anomaly/flux(local_turf, has_changed_lifespan ? rand(25 SECONDS, 35 SECONDS) : null, FALSE, explosive) + if(GRAVITATIONAL_ANOMALY) + new /obj/effect/anomaly/grav(local_turf, has_changed_lifespan ? rand(20 SECONDS, 30 SECONDS) : null, FALSE) + if(PYRO_ANOMALY) + new /obj/effect/anomaly/pyro(local_turf, has_changed_lifespan ? rand(15 SECONDS, 25 SECONDS) : null, FALSE) + if(HALLUCINATION_ANOMALY) + new /obj/effect/anomaly/hallucination/supermatter(local_turf, has_changed_lifespan ? rand(15 SECONDS, 25 SECONDS) : null, FALSE) + if(BIOSCRAMBLER_ANOMALY) + new /obj/effect/anomaly/bioscrambler/docile(local_turf, null, FALSE) + if(DIMENSIONAL_ANOMALY) + new /obj/effect/anomaly/dimensional(local_turf, null, FALSE) diff --git a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm new file mode 100644 index 0000000000..2a64422a5d --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm @@ -0,0 +1,89 @@ +/obj/effect/anomaly/bioscrambler + name = "bioscrambler anomaly" + icon_state = "bioscrambler" + anomaly_core = /obj/item/assembly/signaler/anomaly/bioscrambler + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + layer = ABOVE_MOB_LAYER + lifespan = ANOMALY_COUNTDOWN_TIMER * 2 + + /// Who are we moving towards? + var/datum/weakref/pursuit_target + /// Cooldown for every anomaly pulse + COOLDOWN_DECLARE(pulse_cooldown) + /// How many seconds between each anomaly pulse + var/pulse_delay = 10 SECONDS + /// Range of anomaly pulse + var/range = 2 + +/obj/effect/anomaly/bioscrambler/Initialize(mapload, new_lifespan, drops_core) + . = ..() + pursuit_target = WEAKREF(find_nearest_target()) + +/obj/effect/anomaly/bioscrambler/anomalyEffect(seconds_per_tick) + . = ..() + if(!COOLDOWN_FINISHED(src, pulse_cooldown)) + return + + new /obj/effect/temp_visual/circle_wave/bioscrambler(get_turf(src)) + playsound(src, 'sound/effects/cosmic_energy.ogg', vol = 50, vary = TRUE) + COOLDOWN_START(src, pulse_cooldown, pulse_delay) + for(var/mob/living/carbon/human/nearby in viewers(range, src)) + var/susceptibility = GetAnomalySusceptibility(nearby) + if(prob(susceptibility * 100)) + randmutb(nearby) + domutcheck(nearby, null) + nearby.balloon_alert(nearby, "something has changed about you") + +/obj/effect/anomaly/bioscrambler/move_anomaly() + update_target() + if(isnull(pursuit_target)) + return ..() + var/turf/step_turf = get_step(src, get_dir(src, pursuit_target.resolve())) + step_to(src, step_turf) + +/obj/effect/anomaly/bioscrambler/proc/update_target() + var/mob/living/current_target = pursuit_target?.resolve() + if(QDELETED(current_target)) + pursuit_target = null + if(!isnull(pursuit_target) && prob(80)) + return + var/mob/living/new_target = find_nearest_target() + if(isnull(new_target)) + pursuit_target = null + return + if(new_target == current_target) + return + if(isbelly(new_target.loc) || istype(new_target.loc, /area/crew_quarters)) + return + current_target = new_target + pursuit_target = WEAKREF(new_target) + +/obj/effect/anomaly/bioscrambler/proc/find_nearest_target() + var/closest_distance = INFINITY + var/mob/living/carbon/closest_target = null + for(var/mob/living/carbon/target in GLOB.player_list) + if(target.z != z) + continue + if(SEND_SIGNAL(target, COMSIG_CHECK_FOR_GODMODE) & COMSIG_GODMODE_CANCEL) + continue + if(target.stat >= UNCONSCIOUS) + continue + if(istype(get_area(target), /area/crew_quarters)) + continue + var/distance_from_target = get_dist(src, target) + if(distance_from_target >= closest_distance) + continue + closest_distance = distance_from_target + closest_target = target + + return closest_target + +/// A bioscrambler anomaly subtype which does not pursue people, for purposes of a space ruin +/obj/effect/anomaly/bioscrambler/docile + +/obj/effect/anomaly/bioscrambler/docile/update_target() + return + +/obj/effect/anomaly/bioscrambler/detonate() + COOLDOWN_RESET(src, pulse_cooldown) + anomalyEffect() diff --git a/code/game/objects/effects/anomalies/anomalies_bluespace.dm b/code/game/objects/effects/anomalies/anomalies_bluespace.dm new file mode 100644 index 0000000000..a079eb15ce --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_bluespace.dm @@ -0,0 +1,94 @@ +/obj/effect/anomaly/bluespace + name = "bluespace anomaly" + icon_state = "bluespace" + density = TRUE + anomaly_core = /obj/item/assembly/signaler/anomaly/bluespace + ///range from which we can teleport someone + var/teleport_range = 1 + ///Distance we can teleport someone passively + var/teleport_distance = 4 + +/obj/effect/anomaly/bluespace/Initialize(mapload, new_lifespan) + . = ..() + apply_wibbly_filters(src) + +/obj/effect/anomaly/bluespace/anomalyEffect() + ..() + for(var/mob/living/M in range(teleport_range, src)) + var/susceptibility = GetAnomalySusceptibility(M) + if(prob(100 * susceptibility)) + do_teleport(M, locate(M.x, M.y, M.z), teleport_distance, channel = TELEPORT_CHANNEL_BLUESPACE) + +/obj/effect/anomaly/bluespace/Bumped(atom/movable/AM) + if(isliving(AM) && prob(100 * GetAnomalySusceptibility(AM))) + do_teleport(AM, locate(AM.x, AM.y, AM.z), 8, channel = TELEPORT_CHANNEL_BLUESPACE) + +/obj/effect/anomaly/bluespace/detonate() + playsound(src, 'sound/effects/cosmic_energy.ogg', vol = 50) + + var/turf/impact_turf = pick(get_area_turfs(impact_area)) + if(!impact_area) + return + + var/obj/item/radio/beacon/chosen + var/list/possible = list() + for(var/obj/item/radio/beacon in GLOB.all_beacons) + var/turf/turf = get_turf(beacon) + if(!turf) + continue + if(!check_teleport_valid(src, turf)) + continue + possible += beacon + + if(possible.len > 0) + chosen = pick(possible) + + if(!chosen) + return + + var/turf/beacon_turf = get_turf(chosen) + + playsound(beacon_turf, 'sound/effects/phasein.ogg', 100, TRUE) + + var/datum/announcement/priority/announcement = new/datum/announcement/priority() + announcement.Announce("Massive bluespace translocation detected", "Anomaly Alert") + + var/list/flashers = list() + for(var/mob/living/living in viewers(beacon_turf, null)) + if(living.flash_eyes()) + flashers += living + + var/y_distance = beacon_turf.y - impact_turf.y + var/x_distance = beacon_turf.x - impact_turf.x + var/list/available_turfs = RANGE_TURFS(12, beacon_turf) + for(var/atom/movable/movable in urange(12, impact_turf)) + if(istype(movable, /obj/item/radio/beacon) || iseffect(movable) || isEye(movable)) + continue + if(movable.anchored) + continue + + var/turf/newloc = locate(movable.x + x_distance, movable.y + y_distance, beacon_turf.z) || pick(available_turfs) + do_teleport(movable, newloc, no_effects = TRUE) + + if(isliving(movable) && !(movable in flashers)) + var/mob/living/give_sparkles = movable + give_sparkles.flash_eyes() + +/obj/effect/anomaly/bluespace/stabilize(anchor, has_core) + . = ..() + teleport_range = 0 + +/obj/effect/anomaly/bluespace/big + immortal = TRUE + teleport_range = 2 + teleport_distance = 12 + anomaly_core = null + +/obj/effect/anomaly/bluespace/big/Initialize(mapload, new_lifespan) + . = ..() + transform *= 3 + +/obj/effect/temp_visual/circle_wave/bluespace + color = COLOR_BLUE_LIGHT + duration = 1 SECONDS + amount_to_scale = 5 diff --git a/code/game/objects/effects/anomalies/anomalies_dimensional.dm b/code/game/objects/effects/anomalies/anomalies_dimensional.dm new file mode 100644 index 0000000000..be5dcc94f7 --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_dimensional.dm @@ -0,0 +1,90 @@ +/obj/effect/anomaly/dimensional + name = "dimensional anomaly" + icon_state = "dimensional" + anomaly_core = /obj/item/assembly/signaler/anomaly/dimensional + lifespan = ANOMALY_COUNTDOWN_TIMER * 20 + move_chance = 0 + /// Range of effect, if left alone anomaly will convert a 2(range)+1 squared area. + var/range = 3 + /// List of turfs this anomaly will try to transform before relocating + var/list/turf/target_turfs = list() + /// Current anomaly 'theme', dictates what tiles to create. + var/datum/dimension_theme/theme + /// Effect displaying on the anomaly to represent the theme. + var/mutable_appearance/theme_icon + /// How many times we can still teleport. Delete self if it hits 0 and we try to teleport. If immortal, will simply stay where it is + var/teleports_left + /// Minimum teleports it will do before going away permanently + var/minimum_teleports = 1 + /// Maximum teleports it will do before going away permanently + var/maximum_teleports = 4 + +/obj/effect/anomaly/dimensional/Initialize(mapload, new_lifespan, drops_core) + . = ..() + overlays += mutable_appearance('icons/effects/effects.dmi', "dimensional_overlay") + + animate(src, transform = matrix()*0.85, time = 3, loop = -1) + animate(transform = matrix(), time = 3, loop = -1) + + teleports_left = rand(minimum_teleports, maximum_teleports) + +/obj/effect/anomaly/dimensional/Destroy() + theme = null + target_turfs = null + return ..() + +/obj/effect/anomaly/dimensional/anomalyEffect(seconds_per_tick) + . = ..() + transmute_area() + +/obj/effect/anomaly/dimensional/proc/transmute_area() + if(!theme) + prepare_area() + if(!target_turfs.len) + if(teleports_left <= 0 && !immortal) + detonate() + return + teleports_left-- + relocate() + return + + var/turf/affected_turf = target_turfs[1] + theme.apply_theme(affected_turf, show_effect = TRUE) + target_turfs -= affected_turf + +/obj/effect/anomaly/dimensional/proc/prepare_area(new_theme_path) + if(!new_theme_path) + new_theme_path = pick(subtypesof(/datum/dimension_theme)) + + theme = new new_theme_path + + apply_theme_icon() + + target_turfs = list() + for(var/turf/turf in spiral_range_turfs(range, src)) + if(theme.can_convert(turf)) + target_turfs += turf + +/obj/effect/anomaly/dimensional/proc/apply_theme_icon() + overlays -= theme_icon + theme_icon = mutable_appearance(theme.icon, theme.icon_state, FLOAT_LAYER -1, appearance_flags = appearance_flags | RESET_TRANSFORM) + theme_icon.blend_mode = BLEND_INSET_OVERLAY + overlays += theme_icon + +/obj/effect/anomaly/dimensional/proc/relocate() + var/datum/anomaly_placer/placer = new() + var/area/new_area = placer.find_valid_area() + var/turf/new_turf = placer.find_valid_turf(new_area) + + var/datum/announcement/priority/announcement = new/datum/announcement/priority() + announcement.Announce("Dimensional instability relocated. Expected location: [new_area.name].", "Anomaly Alert") + src.forceMove(new_turf) + prepare_area() + +/obj/effect/anomaly/dimensional/detonate() + qdel(src) + +/obj/effect/temp_visual/transmute_tile_flash + icon = 'icons/effects/effects.dmi' + icon_state = "shieldsparkles" + duration = 3 diff --git a/code/game/objects/effects/anomalies/anomalies_dimensional_themes.dm b/code/game/objects/effects/anomalies/anomalies_dimensional_themes.dm new file mode 100644 index 0000000000..80fded840c --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_dimensional_themes.dm @@ -0,0 +1,264 @@ +/** + * Datum which describes a theme and replaces turfs and objects in specified locations to match that theme + */ +/datum/dimension_theme + /// Human readable name of the theme + var/name = "Unnamed Theme" + /// An icon to display to represent the theme + var/icon/icon = 'icons/obj/stacks.dmi' + /// Icon state to use to represent the theme + var/icon_state + /// Typepath of custom material to use for objects. + var/datum/material/material + /// Sound to play when transforming a tile + var/sound = 'sound/effects/blind.ogg' + /// Weighted list of turfs to replace the floor with. + var/list/replace_floors = list(/turf/simulated/floor/tiled = 1) + /// Typepath of turf to replace walls with. + var/turf/replace_walls = /turf/simulated/wall + /// List of weighted lists for object replacement. Key is an original typepath, value is a weighted list of typepaths to replace it with. + var/list/replace_objs = list( + /obj/structure/bed/chair = list(/obj/structure/bed/chair = 1), + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock = 1, /obj/machinery/door/airlock/glass = 1), + /obj/structure/table = list(/obj/structure/table = 1), + /obj/structure/toilet = list(/obj/structure/toilet = 1) + ) + /// List of random spawns to place in completely open turfs + var/list/random_spawns + /// Prob of placing a random spawn in a completely open turf + var/random_spawn_chance = 0 + /// Typepath of full-size windows which will replace existing ones + /// These need to be separate from replace_objs because we don't want to replace dir windows with full ones and they share typepath + var/obj/structure/window/replace_window + /// Colour to recolour windows with, replaced by material colour if material was specified. + var/window_colour = "#ffffff" + +/datum/dimension_theme/New() + if(material) + var/datum/material/using_mat = GET_MATERIAL_REF(material) + window_colour = using_mat.icon_colour + + +/** + * Applies themed transformation to the provided turf. + * + * Arguments + * * affected_turf - Turf to transform. + * * skip_sound - If the sound shouldn't be played. + * * show_effect - if the temp visual effect should be shown. + */ +/datum/dimension_theme/proc/apply_theme(turf/affected_turf, skip_sound = FALSE, show_effect = FALSE) + if(!replace_turf(affected_turf)) + return + if(!skip_sound) + playsound(affected_turf, sound, 100, TRUE) + if(show_effect) + new /obj/effect/temp_visual/transmute_tile_flash(affected_turf) + for(var/obj/object in affected_turf) + replace_object(object) + if(length(random_spawns) && prob(random_spawn_chance)) + var/random_spawn_picked = pick(random_spawns) + new random_spawn_picked(affected_turf) + +/datum/dimension_theme/proc/can_convert(var/turf/affected_turf) + if(isspace(affected_turf)) + return FALSE + if(isfloorturf(affected_turf)) + if(istype(get_area(affected_turf), /area/holodeck)) + return FALSE + return replace_floors.len > 0 + if(iswall(affected_turf)) + return TRUE + return FALSE + +/datum/dimension_theme/proc/replace_turf(turf/affected_turf) + PROTECTED_PROC(TRUE) + + if(isfloorturf(affected_turf)) + if(istype(get_area(affected_turf), /area/holodeck) || istype(get_area(affected_turf), /area/crew_quarters)) + return FALSE + return transform_floor(affected_turf) + + if(!iswall(affected_turf)) + return FALSE + + affected_turf.ChangeTurf(replace_walls) + return TRUE + +/** + * Replaces the provided floor turf with a different one. + * + * Arguments + * * affected_floor - Floor turf to transform. + */ +/datum/dimension_theme/proc/transform_floor(turf/affected_floor) + PROTECTED_PROC(TRUE) + + if(replace_floors.len == 0) + return FALSE + affected_floor.ChangeTurf(pick_weight(replace_floors)) + + return TRUE + +/** + * Replaces the provided object with a different one. + * + * Arguments + * * object - Object to replace. + */ +/datum/dimension_theme/proc/replace_object(obj/object) + PROTECTED_PROC(TRUE) + + if(istype(object, /obj/structure/window) && window_colour) + object.color = window_colour + return + + if(istype(object, /obj/structure/table) && material) + var/obj/structure/table/table = object + table.material = material + table.update_connections(TRUE) + table.update_icon() + table.update_desc() + table.update_material() + return + + if(istype(object, /obj/machinery/light)) + var/obj/machinery/light/light = object + light.brightness_color = window_colour + light.brightness_color_ns = window_colour + light.set_light(0) + light.update() + + var/replace_path = get_replacement_object_typepath(object) + if(!replace_path) + return + var/obj/new_object = new replace_path(object.loc) + if(istype(object, /obj/machinery/door/airlock)) + if(istype(new_object, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/airlock = object + var/obj/machinery/door/airlock/new_airlock = new_object + new_airlock.req_access = airlock.req_one_access?.Copy() + new_airlock.locked = airlock.locked + if(istype(object, /obj/machinery/door/airlock/multi_tile)) + for(var/turf/location in object.locs) + if(location == object.loc) + continue + var/obj/machinery/door/airlock/long_airlock = new replace_path(location) + long_airlock.req_access = airlock.req_one_access?.Copy() + long_airlock.locked = airlock.locked + long_airlock.name = airlock.name + new_object.name = object.name + qdel(object) + +/** + * Returns the typepath of an object to replace the provided object. + * + * Arguments + * * object - Object to transform. + */ +/datum/dimension_theme/proc/get_replacement_object_typepath(obj/object) + PROTECTED_PROC(TRUE) + + for(var/type in replace_objs) + if(istype(object, type)) + return pick_weight(replace_objs[type]) + +/datum/dimension_theme/gold + name = "Gold" + icon_state = "sheet-gold_2" + material = /datum/material/gold + replace_floors = list(/turf/simulated/floor/tiled/material/gold = 1) + replace_objs = list( + /obj/structure/bed/chair = list(/obj/structure/bed/chair = 1), + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/gold = 1), + /obj/structure/table = list(/obj/structure/table/gold = 1), + /obj/structure/toilet = list(/obj/structure/toilet = 1) + ) + replace_walls = /turf/simulated/wall/gold + +/datum/dimension_theme/radioactive + name = "Radioactive" + icon_state = "sheet-uranium_2" + material = /datum/material/uranium + replace_floors = list(/turf/simulated/floor/tiled/material/uranium = 1) + replace_objs = list( + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/uranium = 1), + ) + replace_walls = /turf/simulated/wall/uranium + sound = 'sound/items/Welder.ogg' + +/datum/dimension_theme/wood + name = "Wood" + icon_state = "sheet-plank" + material = /datum/material/wood + replace_floors = list(/turf/simulated/floor/wood = 1) + replace_objs = list( + /obj/structure/bed/chair = list(/obj/structure/bed/chair/wood = 1), + /obj/machinery/door/airlock = list(/obj/structure/simple_door/wood = 1), + /obj/structure/table = list(/obj/structure/table/woodentable = 1) + ) + replace_walls = /turf/simulated/wall/wood +/* +/datum/dimension_theme/meat + name = "Meat" + icon = 'icons/obj/food.dmi' + icon_state = "meat" + material = /datum/material/flesh + +/datum/dimension_theme/alien + name = "Alien" + icon = 'icons/obj/abductor.dmi' + icon_state = "circuit" + replace_floors = list(/turf/simulated/floor/redgrid = 1, /turf/simulated/floor/greengrid = 1, /turf/simulated/floor/bluegrid = 1) + replace_objs = list( + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/alien = 1), + /obj/structure/table = list(/obj/structure/table/alien = 1, /obj/structure/table/alien/blue = 1) + ) +*/ +/datum/dimension_theme/natural + name = "Natural" + icon = 'icons/obj/plants.dmi' + icon_state = "plant-24" + window_colour = "#0b8011ff" + replace_floors = list(/turf/simulated/floor/grass = 1) + replace_walls = /turf/simulated/wall/wood + replace_objs = list( + /obj/structure/bed/chair = list(/obj/structure/bed/chair/wood = 3, /obj/structure/bed/chair/wood/wings = 1), + /obj/machinery/door/airlock = list(/obj/structure/simple_door/wood = 1), + ) + random_spawns = list( + /obj/structure/flora/grass/green = 3, + /obj/structure/flora/bush = 3, + /mob/living/simple_mob/vore/bee = 1, + ) + random_spawn_chance = 10 + +/datum/dimension_theme/phoron + name = "Phoron" + icon_state = "sheet-phoron_2" + material = /datum/material/phoron + replace_floors = list(/turf/simulated/floor/tiled/material/phoron = 1) + replace_objs = list( + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/phoron = 1), + ) + replace_walls = /turf/simulated/wall/phoron + +/datum/dimension_theme/glass + name = "Glass" + icon_state = "sheet-glass_2" + material = /datum/material/glass + replace_floors = list(/turf/simulated/floor/glass = 1) + replace_objs = list( + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/glass = 1), + /obj/structure/table = list(/obj/structure/table/glass = 1) + ) + replace_walls = /obj/structure/window/basic/full + +/datum/dimension_theme/snow + name = "Snow" + icon_state = "sheet-snow_2" + material = /datum/material/snow + replace_floors = list(/turf/simulated/floor/snow = 10, /turf/simulated/floor/outdoors/ice = 1) + replace_objs = list( + /obj/machinery/door/airlock = list(/obj/structure/simple_door/) + ) diff --git a/code/game/objects/effects/anomalies/anomalies_flux.dm b/code/game/objects/effects/anomalies/anomalies_flux.dm new file mode 100644 index 0000000000..788b84f54c --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_flux.dm @@ -0,0 +1,63 @@ +/obj/effect/anomaly/flux + name = "flux wave anomaly" + icon_state = "flux" + density = TRUE + anomaly_core = /obj/item/assembly/signaler/anomaly/flux + var/canshock = FALSE + var/shockdamage = 20 + var/emp_zap = FLUX_EMP + +/obj/effect/anomaly/flux/Initialize(mapload, new_lifespan, emp_zap = FLUX_EMP) + . = ..() + src.emp_zap = emp_zap + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered) + ) + AddElement(/datum/element/connect_loc, loc_connections) + apply_wibbly_filters(src) + +/obj/effect/anomaly/flux/anomalyEffect() + ..() + canshock = TRUE + for(var/mob/living/M in range(0, src)) + mobShock(M) + +/obj/effect/anomaly/flux/proc/on_entered(datum/source, atom/movable/AM) + SIGNAL_HANDLER + mobShock(AM) + +/obj/effect/anomaly/flux/Bump(atom/A) + mobShock(A) + +/obj/effect/anomaly/flux/Bumped(atom/movable/AM) + mobShock(AM) + +/obj/effect/anomaly/flux/attack_hand(mob/living/user) + mobShock(user) + . = ..() + +/obj/effect/anomaly/flux/attackby(obj/item/I, mob/user) + mobShock(user) + . = ..() + +/obj/effect/anomaly/flux/proc/mobShock(mob/living/M) + if(canshock && istype(M)) + canshock = FALSE + M.electrocute_act(shockdamage, name) + +/obj/effect/anomaly/flux/detonate() + switch(emp_zap) + if(FLUX_EMP) + empulse(src, 4, 16) + explosion(src, heavy_impact_range = 1, light_impact_range = 4, flash_range = 6) + if(FLUX_LIGHT_EMP) + empulse(src, 4, 6) + explosion(src, light_impact_range = 3, flash_range = 6) + if(FLUX_NO_EMP) + new /obj/effect/effect/sparks(loc) + +/obj/effect/anomaly/flux/minor + anomaly_core = null + +/obj/effect/anomaly/flux/minor/Initialize(mapload, new_lifespan, emp_zap = FLUX_NO_EMP) + return ..() diff --git a/code/game/objects/effects/anomalies/anomalies_gravity.dm b/code/game/objects/effects/anomalies/anomalies_gravity.dm new file mode 100644 index 0000000000..7202b676ef --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_gravity.dm @@ -0,0 +1,89 @@ +/obj/effect/anomaly/grav + name = "gravitational anomaly" + icon_state = "gravity" + density = FALSE + anomaly_core = /obj/item/assembly/signaler/anomaly/grav + var/boing = FALSE + var/object_launch_prob = 20 + +/obj/effect/anomaly/grav/Initialize(mapload, new_lifespan) + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + apply_wibbly_filters(src) + +/obj/effect/anomaly/grav/anomalyEffect(seconds_per_tick) + ..() + boing = TRUE + for(var/obj/O in orange(4, src)) + if(!O.anchored) + step_towards(O, src) + for(var/mob/living/M in range(0, src)) + if(!M.can_overcome_gravity()) + gravShock(M) + for(var/mob/living/M in range(4, src)) + if(!M.can_overcome_gravity()) + step_towards(M, src) + for(var/obj/O in range(0, src)) + if(O.anchored) + continue + var/mob/living/target = locate() in view(4, src) + if(target && !target.stat && prob(object_launch_prob)) + O.throw_at(target, 5, 10) + +/obj/effect/anomaly/grav/proc/on_entered(datum/source, atom/movable/AM) + SIGNAL_HANDLER + gravShock(AM) + +/obj/effect/anomaly/grav/Bump(atom/A) + gravShock(A) + +/obj/effect/anomaly/grav/Bumped(atom/movable/AM) + gravShock(AM) + +/obj/effect/anomaly/grav/proc/gravShock(mob/living/living_debris) + if(boing && isliving(living_debris) && !living_debris.stat && !living_debris.can_overcome_gravity()) + living_debris.SetStunned(2) + var/atom/target = get_edge_target_turf(living_debris, get_dir(src, get_step_away(living_debris, src))) + living_debris.throw_at(target, 5, 1) + boing = FALSE + +/obj/effect/anomaly/grav/detonate() + new /obj/effect/temp_visual/circle_wave/gravity(get_turf(src)) + playsound(src, 'sound/effects/cosmic_energy.ogg', vol = 50) + +/* // Unfortunately couldn't get this one working. Maybe in the future. +/obj/effect/anomaly/grav/high + var/datum/proximity_monitor/advanced/gravity/grav_field + +/obj/effect/anomaly/grav/high/Initialize(mapload, new_lifespan) + . = ..() + INVOKE_ASYNC(src, PROC_REF(setup_grav_field)) + +/obj/effect/anomaly/grav/high/proc/setup_grav_field() + grav_field = new(src, 7, TRUE, rand(0, 3)) + +/obj/effect/anomaly/grav/high/detonate() + ..() + for(var/obj/machinery/gravity_generator/main/the_generator as anything in GLOB.machines) + if(is_on_same_plane_or_station(the_generator.z)) + the_generator.breaker = FALSE + the_generator.set_power() + the_generator.charge_count = 10 + +/obj/effect/anomaly/grav/high/Destroy() + QDEL_NULL(grav_field) + . = ..() + +/obj/effect/anomaly/grav/high/big + immortal = TRUE + anomaly_core = null + +/obj/effect/anomaly/grav/high/big/Initialize(mapload, new_lifespan) + . = ..() + transform *= 3 +*/ +/obj/effect/temp_visual/circle_wave/gravity + color = COLOR_NAVY diff --git a/code/game/objects/effects/anomalies/anomalies_hallucination.dm b/code/game/objects/effects/anomalies/anomalies_hallucination.dm new file mode 100644 index 0000000000..33e0156a11 --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_hallucination.dm @@ -0,0 +1,109 @@ +/obj/effect/anomaly/hallucination + name = "hallucination anomaly" + icon_state = "hallucination" + anomaly_core = /obj/item/assembly/signaler/anomaly/hallucination + /// Time passed since the last effect, increased by seconds_per_tick of the SSobj + var/ticks = 0 + /// How many seconds between each small hallucination pulses + var/release_delay = 5 + /// Messages sent to people feeling the pulses + var/static/list/messages = list( + span_warning("You feel your conscious mind fall apart!"), + span_warning("Reality warps around you!"), + span_warning("Something's whispering around you!"), + span_warning("You are going insane!"), + ) + ///Do we spawn misleading decoys? + var/spawn_decoys = TRUE + +/obj/effect/anomaly/hallucination/Initialize(mapload, new_lifespan) + . = ..() + apply_wibbly_filters(src) + generate_decoys() + +/obj/effect/anomaly/hallucination/anomalyEffect(seconds_per_tick) + . = ..() + ticks += seconds_per_tick + if(ticks < release_delay) + return + ticks -= release_delay + if(!isturf(loc)) + return + + for(var/mob/living/carbon/human/hallucinator in viewers(5, src)) + var/susceptibility = GetAnomalySusceptibility(hallucinator) + if(prob(susceptibility * 100)) + hallucinator.hallucination += 10 + if(prob(20)) + to_chat(hallucinator, pick(messages)) + if(prob(10)) + to_chat(hallucinator, span_danger("Your nose bleeds!")) + hallucinator.drip(1) + +/obj/effect/anomaly/hallucination/detonate() + if(isturf(loc)) + return + + for(var/mob/living/carbon/human/hallucinator in viewers(10, src)) + to_chat(hallucinator, pick(messages)) + hallucinator.hallucination += 25 + to_chat(hallucinator, span_danger("Your nose bleeds!")) + hallucinator.drip(1) + +/obj/effect/anomaly/hallucination/proc/generate_decoys() + if(!spawn_decoys) + return + + for(var/turf/floor in orange(1, src)) + if(prob(35)) + new /obj/effect/anomaly/hallucination/decoy(floor) + +/obj/effect/anomaly/hallucination/decoy + anomaly_core = null + var/report_text + +/obj/effect/anomaly/hallucination/decoy/Initialize(mapload, new_lifespan) + . = ..() + report_text = pick( + "[src]'s unstable field is fluctuating along frequency 9999999.99999, code 9999999.99999. No, no, that can't be right?", + "It doesn't detect anything. It awaits an input, as if you're pointing it towards nothing at all. What?", + "The interface displays [pick("a bad memory from your past", "the frequency numbers in a language you cannot read", "the first 15 digits of Pi", "yourself, from behind, angled at a 3/4ths isometric perspective")]. What the hell?", + "Nothing happens?", + "It reports that you are a [pick("moron", "idiot", "cretin", "lowlife", "worthless denthead", "gump")]. Huh?", + "It tells you to try again, because you're doing it all wrong. What?", + "It occurs to you that the anomaly you're scanning isn't actually there.", + "It's not working. You activate %TOOL% again. Still broken. You activate %TOOL%. You activate %TOOL%. Why isn't this working??", + "Something happens. You can't tell what. The interface on %TOOL% remains blank.", + "What are you even trying to accomplish here? Did you really think that was going to work?", + "Someone behind you whispers the frequency code to you, but you can't quite hear them. The interface on %TOOL% remains blank.", + "For a brief moment, you see yourself traversing a frozen forest, before snapping back to reality. The interface on %TOOL% remains blank.", + "Nothing interesting happens. Are you sure you're actually using it on anything?", + "For a moment you can feel your skin falling off, then blink as the sensation vanishes. What the hell did that mean?", + "The interface reports that you are a complete failure, and have screwed everything up again. Great work.", + "You realize that the formatting of this message is completely wrong, and get confused. Now why would that be?", + "%TOOL% stares back at you. It looks dissapointed, its screen practically saying 'You missed the anomaly, you dolt. There's nothing there!'", + "Nothing. Weird, maybe %TOOL% must be broken or something?", + "You activate %TOOL%. You activate %TOOL%. You activate %TOOL%. You activate %TOOL%. You activate %TOOL%. You activate %TOOL%. You activate %TOOL%. Why isn't it working??", + ) + +/obj/effect/anomaly/hallucination/decoy/anomalyEffect(seconds_per_tick) + if(SPT_PROB(move_chance, seconds_per_tick)) + move_anomaly() + +/obj/effect/anomaly/hallucination/decoy/attackby(obj/item/tool, mob/user, params) + if(istype(tool, /obj/item/analyzer)) + to_chat(user, span_notice("You activate \the [tool]. [replacetext(report_text, "%TOOL%", "[tool]")]")) + return ..() + +/obj/effect/anomaly/hallucination/decoy/detonate() + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + return + +/obj/effect/anomaly/hallucination/decoy/generate_decoys() + return + +///Subtype for the SM that doesn't spawn decoys, because otherwise the whole area gets flooded with dummies. +/obj/effect/anomaly/hallucination/supermatter + spawn_decoys = FALSE diff --git a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm new file mode 100644 index 0000000000..bac080489c --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm @@ -0,0 +1,34 @@ +/obj/effect/anomaly/pyro + name = "pyroclastic anomaly" + icon_state = "pyroclastic" + var/ticks = 0 + /// How many seconds between each gas release + var/releasedelay = 15 SECONDS + anomaly_core = /obj/item/assembly/signaler/anomaly/pyro + +/obj/effect/anomaly/pyro/Initialize(mapload, new_lifespan, drops_core) + . = ..() + apply_wibbly_filters(src) + +/obj/effect/anomaly/pyro/anomalyEffect(seconds_per_tick) + ..() + ticks += seconds_per_tick + if(ticks < releasedelay) + return FALSE + else + ticks -= releasedelay + var/turf/simulated/floor/tile = get_turf(src) + if(istype(tile)) + tile.assume_gas(GAS_PHORON, 10, T20C) + tile.hotspot_expose(700, 400) + return TRUE + +/obj/effect/anomaly/pyro/detonate() + var/turf/simulated/floor/tile = get_turf(src) + if(istype(tile)) + tile.assume_gas(GAS_PHORON, 10, T20C) + tile.hotspot_expose(700, 400) + + var/new_colour = pick(/mob/living/simple_mob/slime/xenobio/red, /mob/living/simple_mob/slime/xenobio/orange) + var/mob/living/simple_mob/slime/xenobio/pyro = new new_colour(tile) + pyro.enrage() diff --git a/code/game/objects/effects/anomalies/anomaly_placer.dm b/code/game/objects/effects/anomalies/anomaly_placer.dm new file mode 100644 index 0000000000..e9d343db56 --- /dev/null +++ b/code/game/objects/effects/anomalies/anomaly_placer.dm @@ -0,0 +1,45 @@ +/datum/anomaly_placer + var/static/list/allowed_areas + var/list/excluded = list( + /area/crew_quarters, + /area/shuttle, + /area/space, + /area/solar, + /area/engineering/engine_room, + /area/maintenance, + /area/holodeck, + /area/ai + ) + +/datum/anomaly_placer/proc/find_valid_area() + var/list/possible_areas = get_station_areas(excluded) + if(!length(possible_areas)) + CRASH("No valid areas for anomaly found.") + + var/area/landing_area = pick(possible_areas) + var/list/turf_test = get_area_turfs(landing_area) + if(!turf_test.len) + CRASH("Anomaly : No valid turfs found for [landing_area] - [landing_area.type]") + + return landing_area + +/datum/anomaly_placer/proc/find_valid_turf(area/target_area) + var/list/valid_turfs = list() + for(var/turf/try_turf as anything in get_area_turfs(target_area)) + if(!is_valid_destination(try_turf)) + continue + valid_turfs += try_turf + + if(!valid_turfs.len) + CRASH("Dimensional anomaly attempted to reach invalid locaton [target_area]") + + return pick(valid_turfs) + +/datum/anomaly_placer/proc/is_valid_destination(turf/tested) + if(isspace(tested)) + return FALSE + if(tested.density) + return FALSE + if(isopenspace(tested)) + return FALSE + return TRUE diff --git a/code/game/objects/effects/anomalies/anomaly_spawner.dm b/code/game/objects/effects/anomalies/anomaly_spawner.dm new file mode 100644 index 0000000000..f9bcf20639 --- /dev/null +++ b/code/game/objects/effects/anomalies/anomaly_spawner.dm @@ -0,0 +1,17 @@ +/obj/effect/spawner/lootdrop/environmentally_safe_anomaly + name = "safe anomaly spawner" + icon_state = "instability" + loot = list( + /obj/effect/anomaly/flux, + /obj/effect/anomaly/bluespace, + /obj/effect/anomaly/hallucination, + /obj/effect/anomaly/bioscrambler/docile + ) + + var/anchor_anomaly = FALSE + +/obj/effect/spawner/lootdrop/environmentally_safe_anomaly/Initialize(mapload) + . = ..() + + var/obj/effect/anomaly/anomaly = . + anomaly.stabilize(anchor = anchor_anomaly) diff --git a/code/game/objects/effects/countdown.dm b/code/game/objects/effects/countdown.dm index 25e5efd31e..adbe20b08e 100644 --- a/code/game/objects/effects/countdown.dm +++ b/code/game/objects/effects/countdown.dm @@ -9,7 +9,7 @@ anchored = TRUE plane = PLANE_GHOSTS color = "#ff0000" - var/text_size = 3 + var/text_size = 3 // Larger values clip when the displayed text is larger than 2 digits var/started = FALSE var/displayed_text var/atom/attached_to @@ -78,7 +78,7 @@ /obj/effect/countdown/singularity_act() return -/* + /obj/effect/countdown/anomaly name = "anomaly countdown" @@ -91,4 +91,3 @@ else var/time_left = max(0, (A.death_time - world.time)/10) return round(time_left) -*/ diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 195d1d6319..f724f1667d 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -113,3 +113,35 @@ pixel_y = rand(-9, 0) . = ..() // VOREStation Add End + +/obj/effect/temp_visual/circle_wave + icon = 'icons/effects/64x64.dmi' + icon_state = "circle_wave" + pixel_x = -16 + pixel_y = -16 + duration = 0.5 SECONDS + color = COLOR_LIME + var/max_alpha = 255 + var/amount_to_scale = 2 + +/obj/effect/temp_visual/circle_wave/Initialize(mapload) + transform = matrix().Scale(0.1) + animate(src, transform = matrix().Scale(amount_to_scale), time = duration, flags = ANIMATION_PARALLEL) + animate(src, alpha = max_alpha, time = duration * 0.6, flags = ANIMATION_PARALLEL) + animate(alpha = 0, time = duration * 0.4) + apply_wibbly_filters(src) + return ..() + +/obj/effect/temp_visual/circle_wave/bioscrambler + color = COLOR_LIME + +/obj/effect/temp_visual/circle_wave/bioscrambler/light + max_alpha = 128 + +/obj/effect/temp_visual/circle_wave/void_conduit + color = COLOR_FULL_TONER_BLACK + duration = 12 SECONDS + amount_to_scale = 12 + +/obj/effect/temp_visual/circle_wave/star_blast + color = COLOR_VOID_PURPLE diff --git a/code/game/objects/items/devices/anomaly.dm b/code/game/objects/items/devices/anomaly.dm new file mode 100644 index 0000000000..0fe6e77191 --- /dev/null +++ b/code/game/objects/items/devices/anomaly.dm @@ -0,0 +1,43 @@ +/obj/item/anomaly_neutralizer + name = "anomaly neutralizer" + desc = "A one-use device capable of instantly neutralizing anomalous or otherworldly entities." + icon = 'icons/obj/devices/tool.dmi' + icon_state = "neutralyzer" + w_class = ITEMSIZE_SMALL + slot_flags = SLOT_BELT + item_flags = NOBLUDGEON + +/obj/item/anomaly_neutralizer/Initialize(mapload) + . = ..() + + AddComponent(/datum/component/effect_remover, \ + success_feedback = "You neutralize %THEEFFECT with %THEWEAPON, frying its circuitry in the process.", \ + on_clear_callback = CALLBACK(src, PROC_REF(on_anomaly_neutralized)), \ + effects_we_clear = list(/obj/effect/anomaly)) + +/obj/item/anomaly_neutralizer/proc/on_anomaly_neutralized(obj/effect/anomaly/target, mob/living/user) + target.anomalyNeutralize() + on_use(target, user) + +/obj/item/anomaly_neutralizer/proc/on_use(obj/effect/target, mob/living/user) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + qdel(src) + +/obj/item/anomaly_releaser + icon = 'icons/obj/devices/syndie_gadget.dmi' + icon_state = "anomaly_releaser" + name = "anomaly releaser" + desc = "Single-use injector that releases and stabilizes anomalies by injecting an unknown substance." + throwforce = 0 + w_class = ITEMSIZE_SMALL + throw_speed = 3 + throw_range = 5 + + ///icon state after being used up + var/used_icon_state = "anomaly_releaser_used" + ///are we used? if used we can't be used again + var/used = FALSE + ///Can we be used infinitely? + var/infinite = FALSE diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 636e2f4495..e6808df9e5 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -160,7 +160,8 @@ body_parts_covered = CHEST armor = list(melee = 40, bullet = 30, laser = 30, energy = 10, bomb = 10, bio = 0, rad = 0) - +// Ooold, old reactive armor. +/* //Reactive armor //When the wearer gets hit, this armor will teleport the user a short distance away (to safety or to more danger, no one knows. That's the fun of it!) /obj/item/clothing/suit/armor/reactive @@ -211,7 +212,7 @@ active = FALSE icon_state = "reactiveoff" ..() - +*/ // Alien armor has a chance to completely block attacks. /obj/item/clothing/suit/armor/alien name = "alien enhancement vest" diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm new file mode 100644 index 0000000000..c0f99555bf --- /dev/null +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -0,0 +1,356 @@ +/obj/item/clothing/suit/armor/reactive_armor_shell + name = "reactive armor shell" + desc = "An experimental suit of armor, awaiting installation of an anomaly core." + icon_state = "reactiveoff" + w_class = ITEMSIZE_COST_LARGE + +/obj/item/clothing/suit/armor/reactive_armor_shell/attackby(obj/item/I, mob/user) + . = ..() + var/static/list/anomaly_armour_types = list( + /obj/effect/anomaly/grav = /obj/item/clothing/suit/armor/reactive/repulse, + /obj/effect/anomaly/flux = /obj/item/clothing/suit/armor/reactive/tesla, + /obj/effect/anomaly/bluespace = /obj/item/clothing/suit/armor/reactive/teleport, + //obj/effect/anomaly/bioscrambler = /obj/item/clothing/suit/armor/reactive/bioscrambling, + /obj/effect/anomaly/hallucination = /obj/item/clothing/suit/armor/reactive/hallucinating, + /obj/effect/anomaly/dimensional = /obj/item/clothing/suit/armor/reactive/barricade, + /obj/effect/anomaly/pyro = /obj/item/clothing/suit/armor/reactive/fire + ) + + if(istype(I, /obj/item/assembly/signaler/anomaly)) + var/obj/item/assembly/signaler/anomaly/anomaly = I + var/armour_path = is_path_in_list(anomaly.anomaly_type, anomaly_armour_types, TRUE) + if(!armour_path) + armour_path = /obj/item/clothing/suit/armor/reactive/stealth + to_chat(user, span_notice("You insert [anomaly] into the chest plate, and the armour gently hums to life.")) + new armour_path(get_turf(src)) + qdel(src) + qdel(anomaly) + return TRUE + +/obj/item/clothing/suit/armor/reactive + name = "reactive armor" + desc = "Doesn't seem to do much for some reason." + icon_state = "reactiveoff" + blood_overlay_type = "armor" + armor = list(melee = 40, bullet = 35, laser = 35, energy = 10, bomb = 10, bio = 0, rad = 0) + var/hit_reaction_chance = 50 + ///Whether the armor will try to react to hits (is it on) + var/active = FALSE + ///This will be true for 30 seconds after an EMP, it makes the reaction effect dangerous to the user. + var/bad_effect = FALSE + ///Message sent when the armor is emp'd. It is not the message for when the emp effect goes off. + var/emp_message = span_warning("The reactive armor has been emp'd! Damn, now it's REALLY gonna not do much!") + ///Message sent when the armor is still on cooldown, but activates. + var/cooldown_message = span_danger("The reactive armor fails to do much, as it is recharging! From what? Only the reactive armor knows.") + ///Duration of the cooldown specific to reactive armor for when it can activate again. + var/reactivearmor_cooldown_duration = 10 SECONDS + ///The cooldown itself of the reactive armor for when it can activate again. + var/reactivearmor_cooldown = 0 + +/obj/item/clothing/suit/armor/reactive/update_icon() + . = ..() + icon_state = "reactive[active ? null : "off"]" + +/obj/item/clothing/suit/armor/reactive/attack_self(mob/user) + active = !active + to_chat(user, span_notice("[src] is now [active ? "active" : "inactive"].")) + update_icon() + add_fingerprint(user) + +/obj/item/clothing/suit/armor/reactive/handle_shield(mob/user, damage, atom/damage_source, mob/attacker, def_zone, attack_text) + if(!active || !prob(hit_reaction_chance)) + return FALSE + if(world.time < reactivearmor_cooldown) + cooldown_activation(user) + return FALSE + if(bad_effect) + return emp_activation(user, damage_source, attack_text, damage) + else + return reactive_activation(user, damage_source, attack_text, damage) + +/obj/item/clothing/suit/armor/reactive/proc/cooldown_activation(var/mob/living/carbon/human/owner) + owner.visible_message(cooldown_message) + +/obj/item/clothing/suit/armor/reactive/proc/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("The reactive armor doesn't do much! No surprises here.")) + return TRUE + +/obj/item/clothing/suit/armor/reactive/proc/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("The reactive armor doesn't do much, despite being emp'd! Besides giving off a special message, of course.")) + return TRUE + +/obj/item/clothing/suit/armor/reactive/emp_act(severity, recursive) + . = ..() + if(bad_effect || !active) + return + visible_message(emp_message) + bad_effect = TRUE + addtimer(VARSET_CALLBACK(src, bad_effect, FALSE), 30 SECONDS) + +/obj/item/clothing/suit/armor/reactive/teleport + name = "reactive teleport armor" + desc = "Someone separated our Research Director from his own head!" + emp_message = span_warning("The reactive armor's teleportation calculations begin spewing errors!") + cooldown_message = span_danger("The reactive teleport system is still recharging! It fails to activate!") + reactivearmor_cooldown_duration = 10 SECONDS + var/tele_range = 6 + +/obj/item/clothing/suit/armor/reactive/teleport/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0) + owner.visible_message(span_danger("The reactive teleport system flings [owner] clear of [attack_text]!")) + playsound(get_turf(owner), 'sound/effects/phasein.ogg', 100, TRUE) + do_teleport(owner, get_turf(owner), tele_range, no_effects = TRUE, channel = TELEPORT_CHANNEL_BLUESPACE) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/teleport/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0) + owner.visible_message(span_danger("The reactive teleport system flings itself clear of [attack_text], leaving someone behind in the process!")) + owner.drop_from_inventory(src, get_turf(src)) + playsound(get_turf(owner),'sound/machines/buzz-sigh.ogg', 50, TRUE) + playsound(get_turf(owner), 'sound/effects/phasein.ogg', 100, TRUE) + do_teleport(src, get_turf(owner), tele_range, no_effects = TRUE, channel = TELEPORT_CHANNEL_BLUESPACE) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return FALSE + + +/obj/item/clothing/suit/armor/reactive/repulse + name = "reactive repulse armor" + desc = "An experimental suit of armor that violently throws back attackers." + cooldown_message = span_danger("The repulse generator is still recharging! It fails to generate a strong enough wave!") + emp_message = span_warning("The repulse generator is reset to default settings...") + +/obj/item/clothing/suit/armor/reactive/repulse/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + playsound(get_turf(owner),'sound/effects/repulse.ogg', 100, TRUE) + owner.visible_message(span_danger("[src] blocks [attack_text], converting the attack into a wave of force!")) + var/turf/owner_turf = get_turf(owner) + var/list/thrown_items = list() + for(var/atom/movable/repulsed in range(owner_turf, 5)) + if(repulsed == owner || repulsed.anchored || thrown_items[repulsed]) + continue + var/throwtarget = get_edge_target_turf(owner_turf, get_dir(owner_turf, get_step_away(repulsed, owner_turf))) + repulsed.throw_at(throwtarget, 10, 1) + thrown_items[repulsed] = repulsed + + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/repulse/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + playsound(get_turf(owner),'sound/effects/repulse.ogg', 100, TRUE) + owner.visible_message(span_danger("[src] does not block [attack_text], and instead generates an attracting force!")) + var/turf/owner_turf = get_turf(owner) + var/list/thrown_items = list() + for(var/atom/movable/repulsed in range(owner_turf, 5)) + if(repulsed == owner || repulsed.anchored || thrown_items[repulsed]) + continue + repulsed.throw_at(owner, 10, 1) + thrown_items[repulsed] = repulsed + + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return FALSE + + +// Tesla + +/obj/item/clothing/suit/armor/reactive/tesla + name = "reactive tesla armor" + desc = "An experimental suit of armor with sensitive detectors hooked up to a huge capacitor grid, with emitters strutting out of it. Zap." + siemens_coefficient = -1 + cooldown_message = span_danger("The tesla capacitors on the reactive tesla armor are still recharging! The armor merely emits some sparks.") + emp_message = span_warning("The tesla capacitors beep ominously for a moment.") + clothing_traits = list(TRAIT_TESLA_SHOCKIMMUNE) + /// How strong are the zaps we give off? + var/zap_power = 2.5e4 + /// How far to the zaps we give off go? + var/zap_range = 20 + +/obj/item/clothing/suit/armor/reactive/tesla/cooldown_activation(mob/living/carbon/human/owner) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() + sparks.set_up(1, 1, src) + sparks.start() + ..() + +/obj/item/clothing/suit/armor/reactive/tesla/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("[src] blocks [attack_text], sending out arcs of lightning!")) + tesla_zap(owner, zap_range, zap_power) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/tesla/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("[src] blocks [attack_text], but pulls a massive charge of energy into [owner] from the surrounding environment!")) + REMOVE_CLOTHING_TRAIT(owner, TRAIT_TESLA_SHOCKIMMUNE) + electrocute_mob(owner, get_area(src), src, 1) + ADD_CLOTHING_TRAIT(owner, TRAIT_TESLA_SHOCKIMMUNE) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +// Sure we could, but- Not really THAT useful. Give them the stealth one. +/obj/item/clothing/suit/armor/reactive/bioscrambling + +// Hallucinating + +/obj/item/clothing/suit/armor/reactive/hallucinating + name = "reactive hallucinating armor" + desc = "An experimental suit of armor with sensitive detectors hooked up to the mind of the wearer, sending mind pulses that causes hallucinations around you." + cooldown_message = span_danger("The connection is currently out of sync... Recalibrating.") + emp_message = span_warning("You feel the backsurge of a mind pulse.") + clothing_traits = list(TRAIT_MADNESS_IMMUNE) + +/obj/item/clothing/suit/armor/reactive/hallucinating/cooldown_activation(mob/living/carbon/human/owner) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() + sparks.set_up(1, 1, src) + sparks.start() + ..() + +/obj/item/clothing/suit/armor/reactive/hallucinating/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("[src] blocks [attack_text], sending out mental pulses!")) + for(var/mob/living/carbon/human/hallucinator in viewers(5, get_turf(src))) + if(hallucinator == owner) + continue + hallucinator.hallucination += 50 + if(prob(10)) + to_chat(hallucinator, span_danger("Your nose bleeds!")) + hallucinator.drip(1) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/hallucinating/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("[src] blocks [attack_text], but pulls a massive charge of mental energy into [owner] from the surrounding environment!")) + owner.hallucination += 75 + to_chat(owner, span_danger("Your nose bleeds!")) + owner.drip(1) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +// When the wearer gets hit, this armor will push people nearby and spawn some blocking objects. +/obj/item/clothing/suit/armor/reactive/barricade + name = "reactive barricade armor" + desc = "An experimental suit of armor that generates barriers from another world when it detects its bearer is in danger." + emp_message = span_warning("The reactive armor's dimensional coordinates are scrambled!") + cooldown_message = span_danger("The reactive barrier system is still recharging! It fails to activate!") + reactivearmor_cooldown_duration = 10 SECONDS + +/obj/item/clothing/suit/armor/reactive/barricade/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + playsound(get_turf(owner),'sound/effects/repulse.ogg', 100, TRUE) + owner.visible_message(span_danger("The reactive armor interposes matter from another world between [src] and [attack_text]!")) + for (var/atom/movable/target in repulse_targets(owner)) + repulse(target, owner) + + var/datum/armour_dimensional_theme/theme = new() + theme.apply_random(get_turf(owner), dangerous = FALSE) + qdel(theme) + + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/barricade/proc/repulse_targets(atom/source) + var/list/push_targets = list() + for (var/atom/movable/nearby_movable in view(1, source)) + if(nearby_movable == source) + continue + if(nearby_movable.anchored) + continue + push_targets += nearby_movable + return push_targets + +/obj/item/clothing/suit/armor/reactive/barricade/proc/repulse(atom/movable/victim, atom/source) + var/dist_from_caster = get_dist(victim, source) + + if(dist_from_caster == 0) + return + + if (isliving(victim)) + to_chat(victim, span_userdanger("You're thrown back by a wave of pressure!")) + var/turf/throwtarget = get_edge_target_turf(source, get_dir(source, get_step_away(victim, source, 1))) + victim.throw_at(throwtarget, 1, 1) + +/obj/item/clothing/suit/armor/reactive/barricade/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("The reactive armor shunts matter from an unstable dimension!")) + var/datum/armour_dimensional_theme/theme = new() + theme.apply_random(get_turf(owner), dangerous = TRUE) + qdel(theme) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return FALSE + +// FIRE +/obj/item/clothing/suit/armor/reactive/fire + name = "reactive incendiary armor" + desc = "An experimental suit of armor with a reactive sensor array rigged to a flame emitter. For the stylish pyromaniac." + cooldown_message = span_danger("The reactive incendiary armor activates, but fails to send out flames as it is still recharging its flame jets!") + emp_message = span_warning("The reactive incendiary armor's targeting system begins rebooting...") + +/obj/item/clothing/suit/armor/reactive/fire/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("[src] blocks [attack_text], sending out jets of flame!")) + playsound(get_turf(owner), 'sound/magic/Fireball.ogg', 100, TRUE) + for(var/mob/living/carbon_victim in range(6, get_turf(src))) + if(carbon_victim != owner) + carbon_victim.adjust_fire_stacks(8) + carbon_victim.ignite_mob() + owner.set_wet_stacks(20) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/fire/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("[src] just makes [attack_text] worse by spewing molten death on [owner]!")) + playsound(get_turf(owner), 'sound/magic/Fireball.ogg', 100, TRUE) + owner.adjust_fire_stacks(12) + owner.ignite_mob() + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return FALSE + +/obj/item/clothing/suit/armor/reactive/stealth + name = "reactive stealth armor" + desc = "An experimental suit of armor that renders the wearer invisible on detection of imminent harm, and creates a decoy that runs away from the owner. You can't fight what you can't see." + cooldown_message = span_danger("The reactive stealth system activates, but is not charged enough to fully cloak!") + emp_message = span_warning("The reactive stealth armor's threat assessment system crashes...") + ///when triggering while on cooldown will only flicker the alpha slightly. this is how much it removes. + var/cooldown_alpha_removal = 50 + ///cooldown alpha flicker- how long it takes to return to the original alpha + var/cooldown_animation_time = 3 SECONDS + ///how long they will be fully stealthed + var/stealth_time = 4 SECONDS + ///how long it will animate back the alpha to the original + var/animation_time = 2 SECONDS + var/in_stealth = FALSE + +/obj/item/clothing/suit/armor/reactive/stealth/cooldown_activation(mob/living/carbon/human/owner) + if(in_stealth) + return + owner.alpha = max(0, owner.alpha - cooldown_alpha_removal) + animate(owner, alpha = initial(owner.alpha), time = cooldown_animation_time) + ..() + +/obj/item/clothing/suit/armor/reactive/stealth/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage) + var/mob/living/simple_mob/illusion/decoy = new(owner.loc) + decoy.copy_appearance(owner) + decoy.copy_overlays(owner, TRUE) + var/datum/ai_holder/AI = decoy.ai_holder + var/turf/rand_turf = pick(get_turf(orange(5, 10))) + AI.give_destination(rand_turf) + owner.alpha = 0 + in_stealth = TRUE + owner.visible_message(span_danger("[owner] is hit by [attack_text] in the chest!")) + addtimer(CALLBACK(src, PROC_REF(end_stealth), owner), stealth_time) + decoy.say("*sidestep") + addtimer(CALLBACK(src, PROC_REF(destroy_illusion), decoy), stealth_time) + QDEL_IN(decoy, stealth_time) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/stealth/proc/end_stealth(mob/living/carbon/human/owner) + in_stealth = FALSE + animate(owner, alpha = initial(owner.alpha), time = animation_time) + +/obj/item/clothing/suit/armor/reactive/stealth/proc/destroy_illusion(mob/illusion) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread() + sparks.set_up(3, 3, illusion) + sparks.start() + QDEL_IN(illusion, animation_time) + +/obj/item/clothing/suit/armor/reactive/stealth/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + if(!isliving(hitby)) + return FALSE + var/mob/living/attacker = hitby + owner.visible_message(span_danger("[src] activates, cloaking the wrong person!")) + attacker.alpha = 0 + addtimer(VARSET_CALLBACK(attacker, alpha, initial(attacker.alpha)), 4 SECONDS) + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return FALSE diff --git a/code/modules/clothing/suits/reactive_armour_dimensional_themes.dm b/code/modules/clothing/suits/reactive_armour_dimensional_themes.dm new file mode 100644 index 0000000000..44d81f4ca2 --- /dev/null +++ b/code/modules/clothing/suits/reactive_armour_dimensional_themes.dm @@ -0,0 +1,96 @@ +#define MAX_BARRIERS 4 +#define MIN_BARRIERS 2 + +/datum/armour_dimensional_theme + var/datum/material/material + var/turf/replace_floor = /turf/simulated/floor/tiled + var/turf/replace_wall = /turf/simulated/wall + var/obj/barricade = /obj/structure/barricade + var/barricade_anchored = TRUE + +/datum/armour_dimensional_theme/proc/apply_random(turf/source, dangerous = FALSE) + var/theme_type + if(dangerous) + theme_type = pick(subtypesof(/datum/armour_dimensional_theme/dangerous)) + else + theme_type = pick(subtypesof(/datum/armour_dimensional_theme/safe)) + var/datum/armour_dimensional_theme/theme = new theme_type() + theme.apply(source) + qdel(theme) + +/datum/armour_dimensional_theme/proc/apply(turf/source) + var/obj/effect/effect/smoke/poof = new(source) + poof.time_to_live = 2 SECONDS + var/list/target_area = get_target_area(source) + for (var/turf/target in target_area) + convert_turf(target) + place_barriers(source, target_area) + +/datum/armour_dimensional_theme/proc/get_target_area(turf/source) + var/list/target_area = RANGE_TURFS(1, source) + for (var/turf/check_turf as anything in target_area) + if(isspace(check_turf)) + target_area -= check_turf + continue + if(istype(get_area(check_turf), /area/holodeck)) + continue + + return target_area + +/datum/armour_dimensional_theme/proc/convert_turf(turf/to_convert) + if(isfloorturf(to_convert)) + var/turf/simulated/floor/floor = to_convert + floor.ChangeTurf(replace_floor) + else if(iswall(to_convert)) + to_convert.ChangeTurf(replace_wall) + +/datum/armour_dimensional_theme/proc/place_barriers(turf/source, list/target_area) + target_area -= source + for(var/turf/check_turf as anything in target_area) + if (!check_turf.density) + continue + target_area -= check_turf + + var/to_place = rand(MIN_BARRIERS, MAX_BARRIERS) + var/list/custom_materials = list() + if(material) + custom_materials = list(GET_MATERIAL_REF(material) = SHEET_MATERIAL_AMOUNT) + + while(target_area.len > 0 && to_place > 0) + var/turf/place_turf = pick(target_area) + place_barrier(place_turf, custom_materials) + target_area -= place_turf + to_place-- + +/datum/armour_dimensional_theme/proc/place_barrier(turf/source) + var/obj/structure/barricade/placed_barricade = new barricade(source, material.name) + if(!barricade_anchored) + placed_barricade.anchored = FALSE + +/datum/armour_dimensional_theme/safe + +/datum/armour_dimensional_theme/safe/natural + replace_wall = /turf/simulated/wall/wood + replace_floor = /turf/simulated/floor/wood + barricade = /obj/structure/barricade + material = /datum/material/wood + +/datum/armour_dimensional_theme/safe/snow + replace_wall = /turf/simulated/wall/snowbrick + replace_floor = /turf/simulated/floor/snow + material = /datum/material/snow + +/datum/armour_dimensional_theme/dangerous + +/datum/armour_dimensional_theme/dangerous/radioactive + replace_wall = /turf/simulated/wall/uranium + replace_floor = /turf/simulated/floor/tiled/material/uranium + material = /datum/material/uranium + +/datum/armour_dimensional_theme/dangerous/phoron + replace_wall = /turf/simulated/wall/phoron + replace_floor = /turf/simulated/floor/tiled/material/phoron + material = /datum/material/phoron + +#undef MAX_BARRIERS +#undef MIN_BARRIERS diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index a5c3204dc3..20f40cde10 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -49,7 +49,7 @@ Gunshots/explosions/opening doors/less rare audio (done) // Traditional hallucinations ///////////////////////////////////////////////////////////////////////////////////////////////////// /mob/living/carbon/proc/handle_hallucinations() - if(get_hallucination_component() || !client) + if(get_hallucination_component() || !client || HAS_TRAIT(src, TRAIT_MADNESS_IMMUNE)) return LoadComponent(/datum/component/hallucinations) diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index 7c76ea6470..990df982ed 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -663,6 +663,9 @@ var/list/mining_overlay_cache = list() M.make_jittery(50) //SHAKY this used to be 1000(seizure) but I toned it to 50 to be less aggressive. if(prob(25)) excavate_find(prob(25), finds[1]) + if(prob(2)) + var/anomaly = pick(FLUX_ANOMALY, GRAVITATIONAL_ANOMALY, PYRO_ANOMALY, HALLUCINATION_ANOMALY) + generate_anomaly(get_turf(src), anomaly, 1, FALSE) else if(rand(1,500) == 1) visible_message(span_notice("An old dusty crate was buried within!")) new /obj/structure/closet/crate/secure/loot(src) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 358e5e8b01..ca55f53ef5 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1255,7 +1255,7 @@ Paralyse(3) if(hallucination) - if(hallucination >= HALLUCINATION_THRESHOLD && !(species.flags & (NO_POISON|IS_PLANT|NO_HALLUCINATION)) ) + if(hallucination >= HALLUCINATION_THRESHOLD && !(species.flags & (NO_POISON|IS_PLANT|NO_HALLUCINATION)) && !HAS_TRAIT(src, TRAIT_MADNESS_IMMUNE)) handle_hallucinations() /* Stop spinning the view, it breaks too much. if(client && prob(5)) diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index e46ac9b879..942a6b8abb 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -301,6 +301,16 @@ if(grav_pulling) supermatter_pull(src) + if(damage) // Start fucking things up + if(get_integrity() < 85 && prob(5)) + generate_anomaly(get_ranged_target_turf(src, pick(GLOB.cardinal), rand(5, 10)), FLUX_ANOMALY) + if(get_integrity() < 75 && prob(5)) + generate_anomaly(get_ranged_target_turf(src, pick(GLOB.cardinal), rand(5, 10)), HALLUCINATION_ANOMALY) + if(get_integrity() < 50 && prob(2)) + generate_anomaly(get_ranged_target_turf(src, pick(GLOB.cardinal), rand(5, 10)), GRAVITATIONAL_ANOMALY) + if(get_integrity() < 25 && prob(0.3)) + generate_anomaly(get_ranged_target_turf(src, pick(GLOB.cardinal), rand(5, 10)), PYRO_ANOMALY) + // Vary volume by power produced. if(power) // Volume will be 1 at no power, ~12.5 at ENERGY_NITROGEN, and 20+ at ENERGY_PHORON. diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm new file mode 100644 index 0000000000..14214aa23b --- /dev/null +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -0,0 +1,92 @@ +/obj/item/assembly/signaler/anomaly + name = "anomaly core" + desc = "The neutralized core of an anomaly. It'd probably be valuable for research." + icon_state = "anomaly_core" + + var/anomaly_type = /obj/effect/anomaly + var/worth = 250 // Pricey... Should be hard-ish to obtain. + +/obj/item/assembly/signaler/anomaly/Initialize(mapload) + . = ..() + if(worth) + AddElement(/datum/element/sellable) + +/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) + if(!signal) + return FALSE + if(signal.encryption != code) + return FALSE + for(var/obj/effect/anomaly/anomaly in get_turf(src)) + anomaly.anomalyNeutralize() + return TRUE + +/obj/item/assembly/signaler/anomaly/attack_self(mob/user) + return + +/obj/item/assembly/signaler/anomaly/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/analyzer)) + to_chat(user, span_notice("Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].")) + return TRUE + + if(istype(W, /obj/item/anomaly_releaser)) + var/obj/item/anomaly_releaser/releaser = W + if(releaser.used) + return FALSE + if(!do_after(user, 3 SECONDS, src)) + return FALSE + + var/obj/item/assembly/signaler/anomaly/core = src + if(!core.anomaly_type) + return FALSE + + var/obj/effect/anomaly/anomaly = new core.anomaly_type(get_turf(core)) + anomaly.stabilize() + + if(!releaser.infinite) + releaser.icon_state = releaser.used_icon_state + releaser.used = TRUE + releaser.name = "used " + name + qdel(src) + return ..() + +/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/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 + +/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/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 + +/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 + +/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 + +/obj/item/assembly/signaler/anomaly/pyro + name = "\improper pyroclastic anomaly core" + desc = "The neutralized core of a pyroclastic anomaly. It feels warm to the touch. It'd probably be valuable for research." + icon_state = "pyro_core" + anomaly_type = /obj/effect/anomaly/pyro diff --git a/code/modules/research/tg/designs/misc_designs.dm b/code/modules/research/tg/designs/misc_designs.dm index f9960b9524..c500ec61ca 100644 --- a/code/modules/research/tg/designs/misc_designs.dm +++ b/code/modules/research/tg/designs/misc_designs.dm @@ -276,3 +276,32 @@ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_BLUESPACE ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_CARGO + +/datum/design_techweb/anomaly_neutralizer + name = "Anomaly Neutralizer" + desc = "An advanced tool capable of instantly neutralizing anomalies, designed to capture the fleeting aberrations created by the engine." + id = "anomaly_neutralizer" + build_type = PROTOLATHE + materials = list(MAT_STEEL = SHEET_MATERIAL_AMOUNT, MAT_GOLD = SHEET_MATERIAL_AMOUNT, MAT_PHORON = SHEET_MATERIAL_AMOUNT * 2.5, MAT_URANIUM = SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/anomaly_neutralizer + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SCIENCE + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design_techweb/reactive_armour + name = "Reactive Armor Shell" + desc = "An experimental suit of armour capable of utilizing an implanted anomaly core to protect the user." + id = "reactive_armour" + build_type = PROTOLATHE + materials = list( + MAT_STEEL = SHEET_MATERIAL_AMOUNT*5, + MAT_URANIUM = SHEET_MATERIAL_AMOUNT*4, + MAT_DIAMOND = SHEET_MATERIAL_AMOUNT*2.5, + MAT_SILVER = SHEET_MATERIAL_AMOUNT*2.5, + MAT_GOLD = SHEET_MATERIAL_AMOUNT*2.5 + ) + build_path = /obj/item/clothing/suit/armor/reactive_armor_shell + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SCIENCE + ) diff --git a/code/modules/research/tg/techwebs/nodes/research_nodes.dm b/code/modules/research/tg/techwebs/nodes/research_nodes.dm index 3e9a280a54..9d23068016 100644 --- a/code/modules/research/tg/techwebs/nodes/research_nodes.dm +++ b/code/modules/research/tg/techwebs/nodes/research_nodes.dm @@ -108,9 +108,9 @@ "xenoarch_multitool", "excavationdrill", "ano_scanner", - // "anomaly_refinery", - // "anomaly_neutralizer", - // "reactive_armour", + //"anomaly_refinery", + "anomaly_neutralizer", + "reactive_armour", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS) announce_channels = list(CHANNEL_SCIENCE) diff --git a/icons/effects/64x64.dmi b/icons/effects/64x64.dmi index 093da559ef..a8fcd83b4a 100644 Binary files a/icons/effects/64x64.dmi and b/icons/effects/64x64.dmi differ diff --git a/icons/effects/anomalies.dmi b/icons/effects/anomalies.dmi new file mode 100644 index 0000000000..e8eb3b4844 Binary files /dev/null and b/icons/effects/anomalies.dmi differ diff --git a/icons/inventory/suit/item.dmi b/icons/inventory/suit/item.dmi index adbfa82c86..f96c024f26 100644 Binary files a/icons/inventory/suit/item.dmi and b/icons/inventory/suit/item.dmi differ diff --git a/icons/inventory/suit/mob.dmi b/icons/inventory/suit/mob.dmi index c9e9edffa6..aefef387b7 100644 Binary files a/icons/inventory/suit/mob.dmi and b/icons/inventory/suit/mob.dmi differ diff --git a/icons/inventory/suit/mob_teshari.dmi b/icons/inventory/suit/mob_teshari.dmi index 7f33109ba9..c735cf82a1 100644 Binary files a/icons/inventory/suit/mob_teshari.dmi and b/icons/inventory/suit/mob_teshari.dmi differ diff --git a/icons/inventory/suit/mob_vox.dmi b/icons/inventory/suit/mob_vox.dmi index 92428f99d3..68ef117117 100644 Binary files a/icons/inventory/suit/mob_vox.dmi and b/icons/inventory/suit/mob_vox.dmi differ diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi index 0f9d337dc5..f4f7433322 100644 Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi index f81d8a42d6..b576c42c23 100644 Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ diff --git a/sound/effects/blind.ogg b/sound/effects/blind.ogg new file mode 100644 index 0000000000..a437392d83 Binary files /dev/null and b/sound/effects/blind.ogg differ diff --git a/sound/effects/cosmic_energy.ogg b/sound/effects/cosmic_energy.ogg new file mode 100644 index 0000000000..c69a41198f Binary files /dev/null and b/sound/effects/cosmic_energy.ogg differ diff --git a/sound/effects/repulse.ogg b/sound/effects/repulse.ogg new file mode 100644 index 0000000000..1adec44f3a Binary files /dev/null and b/sound/effects/repulse.ogg differ diff --git a/sound/magic/Fireball.ogg b/sound/magic/Fireball.ogg new file mode 100644 index 0000000000..af6a011829 Binary files /dev/null and b/sound/magic/Fireball.ogg differ diff --git a/vorestation.dme b/vorestation.dme index def6b739e9..78d55e2c3a 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -50,6 +50,7 @@ #include "code\__defines\airlock_control.dm" #include "code\__defines\alerts.dm" #include "code\__defines\ammunition.dm" +#include "code\__defines\anomaly.dm" #include "code\__defines\appearance.dm" #include "code\__defines\assemblies.dm" #include "code\__defines\assert.dm" @@ -338,6 +339,7 @@ #include "code\__defines\dcs\signals\signals_mob\signals_mob_simple.dm" #include "code\__defines\dcs\signals\signals_mob\signals_mob_spawner.dm" #include "code\__defines\dcs\signals\signals_mob\signals_mob_xenochimera.dm" +#include "code\__defines\research\anomalies.dm" #include "code\__defines\research\research_categories.dm" #include "code\__defines\research\techweb_nodes.dm" #include "code\__defines\traits\_traits.dm" @@ -1559,6 +1561,17 @@ #include "code\game\objects\effects\wire_deleter.dm" #include "code\game\objects\effects\zone_divider.dm" #include "code\game\objects\effects\alien\aliens.dm" +#include "code\game\objects\effects\anomalies\_anomalies.dm" +#include "code\game\objects\effects\anomalies\anomalies_bioscrambler.dm" +#include "code\game\objects\effects\anomalies\anomalies_bluespace.dm" +#include "code\game\objects\effects\anomalies\anomalies_dimensional.dm" +#include "code\game\objects\effects\anomalies\anomalies_dimensional_themes.dm" +#include "code\game\objects\effects\anomalies\anomalies_flux.dm" +#include "code\game\objects\effects\anomalies\anomalies_gravity.dm" +#include "code\game\objects\effects\anomalies\anomalies_hallucination.dm" +#include "code\game\objects\effects\anomalies\anomalies_pyroclastic.dm" +#include "code\game\objects\effects\anomalies\anomaly_placer.dm" +#include "code\game\objects\effects\anomalies\anomaly_spawner.dm" #include "code\game\objects\effects\chem\chemsmoke.dm" #include "code\game\objects\effects\chem\coating.dm" #include "code\game\objects\effects\chem\foam.dm" @@ -1640,6 +1653,7 @@ #include "code\game\objects\items\devices\advnifrepair.dm" #include "code\game\objects\items\devices\ai_detector.dm" #include "code\game\objects\items\devices\aicard.dm" +#include "code\game\objects\items\devices\anomaly.dm" #include "code\game\objects\items\devices\binoculars.dm" #include "code\game\objects\items\devices\body_snatcher_vr.dm" #include "code\game\objects\items\devices\chameleonproj.dm" @@ -2628,6 +2642,8 @@ #include "code\modules\clothing\suits\miscellaneous.dm" #include "code\modules\clothing\suits\miscellaneous_vr.dm" #include "code\modules\clothing\suits\neosuits.dm" +#include "code\modules\clothing\suits\reactive_armour.dm" +#include "code\modules\clothing\suits\reactive_armour_dimensional_themes.dm" #include "code\modules\clothing\suits\shibari.dm" #include "code\modules\clothing\suits\solgov.dm" #include "code\modules\clothing\suits\solgov_vr.dm" @@ -4442,6 +4458,7 @@ #include "code\modules\refinery\equipment\smart_centrifuge.dm" #include "code\modules\research\message_server.dm" #include "code\modules\research\part_replacer.dm" +#include "code\modules\research\anomaly\anomaly_core.dm" #include "code\modules\research\tg\designs.dm" #include "code\modules\research\tg\disks.dm" #include "code\modules\research\tg\rdconsole.dm"