diff --git a/code/__DEFINES/supermatter.dm b/code/__DEFINES/supermatter.dm index 1c4f234680c..e3c68f7265f 100644 --- a/code/__DEFINES/supermatter.dm +++ b/code/__DEFINES/supermatter.dm @@ -52,6 +52,7 @@ #define BIOSCRAMBLER_ANOMALY "bioscrambler_anomaly" #define HALLUCINATION_ANOMALY "hallucination_anomaly" #define VORTEX_ANOMALY "vortex_anomaly" +#define DIMENSIONAL_ANOMALY "dimensional_anomaly" #define SUPERMATTER_COUNTDOWN_TIME 30 SECONDS diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index b97a992ace3..7deb34cf378 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -6,7 +6,8 @@ /obj/effect/anomaly name = "anomaly" desc = "A mysterious anomaly, seen commonly only in the region of space that the station orbits..." - icon_state = "bhole3" + icon = 'icons/effects/anomalies.dmi' + icon_state = "vortex" density = FALSE anchored = TRUE light_range = 3 @@ -24,6 +25,8 @@ var/drops_core = TRUE ///Do we keep on living forever? var/immortal = FALSE + ///Do we stay in one place? + var/immobile = FALSE /obj/effect/anomaly/Initialize(mapload, new_lifespan, drops_core = TRUE) . = ..() @@ -70,7 +73,7 @@ return ..() /obj/effect/anomaly/proc/anomalyEffect(delta_time) - if(DT_PROB(ANOMALY_MOVECHANCE, delta_time)) + if(!immobile && DT_PROB(ANOMALY_MOVECHANCE, delta_time)) step(src,pick(GLOB.alldirs)) /obj/effect/anomaly/proc/detonate() @@ -90,7 +93,6 @@ qdel(src) - /obj/effect/anomaly/attackby(obj/item/weapon, mob/user, params) if(weapon.tool_behaviour == TOOL_ANALYZER) to_chat(user, span_notice("Analyzing... [src]'s unstable field is fluctuating along frequency [format_frequency(aSignal.frequency)], code [aSignal.code].")) @@ -110,6 +112,7 @@ /obj/effect/anomaly/grav name = "gravitational anomaly" + icon = 'icons/effects/effects.dmi' icon_state = "shield2" density = FALSE aSignal = /obj/item/assembly/signaler/anomaly/grav @@ -197,7 +200,7 @@ /obj/effect/anomaly/flux name = "flux wave anomaly" - icon_state = "electricity2" + icon_state = "flux" density = TRUE aSignal = /obj/item/assembly/signaler/anomaly/flux var/canshock = FALSE @@ -320,7 +323,7 @@ /obj/effect/anomaly/pyro name = "pyroclastic anomaly" - icon_state = "mustard" + icon_state = "pyroclastic" var/ticks = 0 /// How many seconds between each gas release var/releasedelay = 10 @@ -366,7 +369,7 @@ /obj/effect/anomaly/bhole name = "vortex anomaly" - icon_state = "bhole3" + icon_state = "vortex" desc = "That's a nice station you have there. It'd be a shame if something happened to it." aSignal = /obj/item/assembly/signaler/anomaly/vortex @@ -430,7 +433,7 @@ /obj/effect/anomaly/bioscrambler name = "bioscrambler anomaly" - icon_state = "bioscrambler_anomaly" + icon_state = "bioscrambler" aSignal = /obj/item/assembly/signaler/anomaly/bioscrambler immortal = TRUE /// Cooldown for every anomaly pulse @@ -502,7 +505,7 @@ /obj/effect/anomaly/hallucination name = "hallucination anomaly" - icon_state = "hallucination_anomaly" + icon_state = "hallucination" aSignal = /obj/item/assembly/signaler/anomaly/hallucination /// Time passed since the last effect, increased by delta_time of the SSobj var/ticks = 0 @@ -546,4 +549,87 @@ ) to_chat(near, span_warning(pick(messages))) +///////////////////// + +/obj/effect/anomaly/dimensional + name = "dimensional anomaly" + icon_state = "dimensional" + aSignal = /obj/item/assembly/signaler/anomaly/dimensional + immortal = TRUE + immobile = TRUE + /// 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 = new() + /// 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 + +/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) + +/obj/effect/anomaly/dimensional/anomalyEffect(delta_time) + . = ..() + transmute_area() + +/** + * Transforms a turf in our prepared area. + */ +/obj/effect/anomaly/dimensional/proc/transmute_area() + if (!theme) + prepare_area() + if (!target_turfs.len) + relocate() + return + + var/turf/affected_turf = target_turfs[1] + new /obj/effect/temp_visual/transmute_tile_flash(affected_turf) + theme.apply_theme(affected_turf) + target_turfs.Remove(affected_turf) + +/** + * Prepare a new area for transformation into a new theme. + */ +/obj/effect/anomaly/dimensional/proc/prepare_area() + var/datum/dimension_theme/themes = new() + theme = themes.get_random_theme() + apply_theme_icon() + + target_turfs = new() + var/list/turfs = spiral_range_turfs(range, src) + for (var/turf/turf in turfs) + if (theme.can_convert(turf)) + target_turfs.Add(turf) + +/** + * Applies an overlay icon based on the current theme. + */ +/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 + +/** + * Moves the anomaly somewhere else and announces it. + */ +/obj/effect/anomaly/dimensional/proc/relocate() + var/datum/anomaly_placer/placer = new() + var/area/new_area = placer.findValidArea() + var/turf/new_turf = placer.findValidTurf(new_area) + + priority_announce("Dimensional instability relocated. Expected location: [new_area.name].", "Anomaly Alert") + src.forceMove(new_turf) + prepare_area() + +/obj/effect/temp_visual/transmute_tile_flash + icon = 'icons/effects/effects.dmi' + icon_state = "shield-flash" + duration = 3 + #undef ANOMALY_MOVECHANCE diff --git a/code/game/objects/effects/anomaly_dimensional_themes.dm b/code/game/objects/effects/anomaly_dimensional_themes.dm new file mode 100644 index 00000000000..a3080b71b9d --- /dev/null +++ b/code/game/objects/effects/anomaly_dimensional_themes.dm @@ -0,0 +1,328 @@ + +/** + * Datum which describes a theme and replaces turfs and objects in specified locations to match that theme + */ +/datum/dimension_theme + /// An icon to display to represent the theme + var/icon/icon + /// 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/magic/blind.ogg' + /// Weighted list of turfs to replace the floor with. + var/list/replace_floors = list(/turf/open/floor/material = 1) + /// Typepath of turf to replace walls with. + var/turf/replace_walls = /turf/closed/wall/material + /// 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/chair = list(/obj/structure/chair/greyscale = 1), \ + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/material = 1, /obj/machinery/door/airlock/material/glass = 1), \ + /obj/structure/table = list(/obj/structure/table/greyscale = 1), \ + /obj/structure/toilet = list(/obj/structure/toilet/greyscale = 1),) + /// 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.greyscale_colors + +/** + * Returns a subtype of dimensional theme. + */ +/datum/dimension_theme/proc/get_random_theme() + var/subtype = pick(subtypesof(/datum/dimension_theme)) + return new subtype() + +/** + * Applies themed transformation to the provided turf. + * + * Arguments + * * affected_turf - Turf to transform. + */ +/datum/dimension_theme/proc/apply_theme(turf/affected_turf) + if (!replace_turf(affected_turf)) + return + playsound(affected_turf, sound, 100, TRUE) + for (var/obj/object in affected_turf) + replace_object(object) + if (material) + apply_materials(affected_turf) + +/** + * Returns true if you actually can transform the provided turf. + * + * Arguments + * * affected_turf - Turf to transform. + */ +/datum/dimension_theme/proc/can_convert(turf/affected_turf) + if (isspaceturf(affected_turf)) + return FALSE + if (isfloorturf(affected_turf)) + if (isindestructiblefloor(affected_turf)) + return FALSE + if (affected_turf.holodeck_compatible) + return FALSE + return replace_floors.len > 0 + if (iswallturf(affected_turf)) + if (isindestructiblewall(affected_turf)) + return FALSE + return TRUE + return FALSE + +/** + * Replaces the provided turf with a different one. + * + * Arguments + * * affected_turf - Turf to transform. + */ +/datum/dimension_theme/proc/replace_turf(turf/affected_turf) + if (isfloorturf(affected_turf)) + if (isindestructiblefloor(affected_turf)) + return FALSE + if (affected_turf.holodeck_compatible) + return FALSE + return transform_floor(affected_turf) + + if (!iswallturf(affected_turf)) + return FALSE + if (isindestructiblewall(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/open/floor/affected_floor) + if (replace_floors.len == 0) + return FALSE + affected_floor.ChangeTurf(pick_weight(replace_floors), flags = CHANGETURF_INHERIT_AIR) + return TRUE + +/** + * Replaces the provided object with a different one. + * + * Arguments + * * object - Object to replace. + */ +/datum/dimension_theme/proc/replace_object(obj/object) + if (istype(object, /obj/structure/window)) + transform_window(object) + return + + var/replace_path = get_replacement_object_typepath(object) + if (!replace_path) + return + var/obj/new_object = new replace_path(object.loc) + new_object.setDir(object.dir) + 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) + for (var/type in replace_objs) + if (istype(object, type)) + return pick_weight(replace_objs[type]) + return + +/** + * Replaces a window with a different window and recolours it. + * This needs its own function because we only want to replace full tile windows. + * + * Arguments + * * object - Object to transform. + */ +/datum/dimension_theme/proc/transform_window(obj/structure/window/window) + if (!window.fulltile) + return + if (!replace_window) + window.add_atom_colour(window_colour, FIXED_COLOUR_PRIORITY) + return + + var/obj/structure/window/new_window = new replace_window(window.loc) + new_window.add_atom_colour(window_colour, FIXED_COLOUR_PRIORITY) + qdel(window) + +#define PERMITTED_MATERIAL_REPLACE_TYPES list(\ + /obj/structure/chair, \ + /obj/machinery/door/airlock, \ + /obj/structure/table, \ + /obj/structure/toilet, \ + /obj/structure/window, \ + /obj/structure/sink,) + +/** + * Returns true if the provided object can have its material modified. + * + * Arguments + * * object - Object to transform. + */ +/datum/dimension_theme/proc/permit_replace_material(obj/object) + for (var/type in PERMITTED_MATERIAL_REPLACE_TYPES) + if (istype(object, type)) + return TRUE + return FALSE + +/** + * Applies a new custom material to the contents of a provided turf. + * + * Arguments + * * affected_turf - Turf to transform. + */ +/datum/dimension_theme/proc/apply_materials(turf/affected_turf) + var/list/custom_materials = list(GET_MATERIAL_REF(material) = MINERAL_MATERIAL_AMOUNT) + + if (istype(affected_turf, /turf/open/floor/material) || istype(affected_turf, /turf/closed/wall/material)) + affected_turf.set_custom_materials(custom_materials) + for (var/obj/thing in affected_turf) + if (!permit_replace_material(thing)) + continue + thing.set_custom_materials(custom_materials) + +#undef PERMITTED_MATERIAL_REPLACE_TYPES + +///////////////////// + +/datum/dimension_theme/gold + icon = 'icons/obj/stack_objects.dmi' + icon_state = "sheet-gold_2" + material = /datum/material/gold + +/datum/dimension_theme/plasma + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "gas_alt" + material = /datum/material/plasma + +/datum/dimension_theme/clown + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "clown" + material = /datum/material/bananium + sound = 'sound/items/bikehorn.ogg' + +/datum/dimension_theme/radioactive + icon = 'icons/obj/ore.dmi' + icon_state = "Uranium ore" + material = /datum/material/uranium + sound = 'sound/items/welder.ogg' + +/datum/dimension_theme/meat + icon = 'icons/obj/food/meat.dmi' + icon_state = "meat" + material = /datum/material/meat + sound = 'sound/items/eatfood.ogg' + +/datum/dimension_theme/pizza + icon = 'icons/obj/food/pizza.dmi' + icon_state = "pizzamargherita" + material = /datum/material/pizza + sound = 'sound/items/eatfood.ogg' + +/datum/dimension_theme/natural + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "map_flower" + window_colour = "#00f7ff" + replace_floors = list(/turf/open/floor/grass = 1) + replace_walls = /turf/closed/wall/mineral/wood/nonmetal + replace_objs = list(\ + /obj/structure/chair = list(/obj/structure/chair/wood = 3, /obj/structure/chair/wood/wings = 1), \ + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1), \ + /obj/structure/table = list(/obj/structure/table/wood = 5, /obj/structure/table/wood/fancy = 1),) + +/datum/dimension_theme/bamboo + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "bamboo" + replace_floors = list(/turf/open/floor/bamboo = 1) + replace_walls = /turf/closed/wall/mineral/bamboo + replace_window = /obj/structure/window/paperframe + replace_objs = list(\ + /obj/structure/chair = list(/obj/structure/chair/stool/bamboo = 1), \ + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1), \ + /obj/structure/table = list(/obj/structure/table/wood = 1),) + +/datum/dimension_theme/icebox + icon = 'icons/obj/clothing/shoes.dmi' + icon_state = "iceboots" + window_colour = "#00f7ff" + material = /datum/material/snow + replace_floors = list(/turf/open/floor/fake_snow = 10, /turf/open/floor/fakeice/slippery = 1) + replace_walls = /turf/closed/wall/mineral/snow + +/datum/dimension_theme/lavaland + icon = 'icons/obj/stack_objects.dmi' + icon_state = "goliath_hide" + window_colour = "#860000" + replace_floors = list(/turf/open/floor/fakebasalt = 5, /turf/open/floor/fakepit = 1) + replace_walls = /turf/closed/wall/mineral/cult + replace_objs = list(\ + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/external/glass/ruin = 1)) + +/datum/dimension_theme/space + icon = 'icons/effects/effects.dmi' + icon_state = "blessed" + window_colour = "#000000" + material = /datum/material/glass + replace_floors = list(/turf/open/floor/fakespace = 1) + replace_walls = /turf/closed/wall/rock/porous + replace_objs = list(/obj/machinery/door/airlock = list(/obj/machinery/door/airlock/external/glass/ruin = 1)) + +/datum/dimension_theme/glass + icon = 'icons/obj/shards.dmi' + icon_state = "small" + material = /datum/material/glass + replace_floors = list(/turf/open/floor/glass = 1) + sound = SFX_SHATTER + +/datum/dimension_theme/fancy + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "fancycrown" + replace_walls = /turf/closed/wall/mineral/wood/nonmetal + +#define FANCY_CARPETS list(\ + /turf/open/floor/eighties, \ + /turf/open/floor/eighties/red, \ + /turf/open/floor/carpet/lone/star, \ + /turf/open/floor/carpet/black, \ + /turf/open/floor/carpet/blue, \ + /turf/open/floor/carpet/cyan, \ + /turf/open/floor/carpet/green, \ + /turf/open/floor/carpet/orange, \ + /turf/open/floor/carpet/purple, \ + /turf/open/floor/carpet/red, \ + /turf/open/floor/carpet/royalblack, \ + /turf/open/floor/carpet/royalblue,) + +/datum/dimension_theme/fancy/New() + replace_floors = list(pick(FANCY_CARPETS) = 1) + replace_objs = list(\ + /obj/structure/chair = list(/obj/structure/chair/comfy = 1), \ + /obj/machinery/door/airlock = list(/obj/machinery/door/airlock/wood = 1, /obj/machinery/door/airlock/wood/glass = 1), \ + /obj/structure/table/wood = list(pick(subtypesof(/obj/structure/table/wood/fancy)) = 1),) + +#undef FANCY_CARPETS + +/datum/dimension_theme/disco + icon = 'icons/obj/lighting.dmi' + icon_state = "lbulb" + material = /datum/material/glass + replace_floors = list(/turf/open/floor/light = 1) + +/datum/dimension_theme/disco/transform_floor(turf/open/floor/affected_floor) + . = ..() + if (!.) + return + var/turf/open/floor/light/disco_floor = affected_floor + disco_floor.currentcolor = pick(disco_floor.coloredlights) + disco_floor.update_appearance() diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 08da280742b..b4e606567e1 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -331,6 +331,9 @@ ///How much damage the trap deals when triggered. var/trap_damage = 20 +/obj/item/restraints/legcuffs/beartrap/prearmed + armed = TRUE + /obj/item/restraints/legcuffs/beartrap/Initialize(mapload) . = ..() update_appearance() diff --git a/code/game/turfs/open/floor/fancy_floor.dm b/code/game/turfs/open/floor/fancy_floor.dm index 06ad9fa16e2..e4b109d12c2 100644 --- a/code/game/turfs/open/floor/fancy_floor.dm +++ b/code/game/turfs/open/floor/fancy_floor.dm @@ -776,6 +776,13 @@ icon_state = "ice_turf-0" base_icon_state = "ice_turf-0" +/turf/open/floor/fakeice/slippery + desc = "Somehow, it is not melting under these conditions. Must be some very thick ice. Just as slippery too." + +/turf/open/floor/fakeice/slippery/Initialize(mapload) + . = ..() + MakeSlippery(TURF_WET_PERMAFROST, INFINITY, 0, INFINITY, TRUE) + /turf/open/floor/fakespace icon = 'icons/turf/space.dmi' icon_state = "0" diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm index 0bbcdbd6093..7fb9cb77f7f 100644 --- a/code/modules/cargo/packs.dm +++ b/code/modules/cargo/packs.dm @@ -1434,6 +1434,16 @@ crate_name = "raw bioscrambler anomaly" crate_type = /obj/structure/closet/crate/secure/science +/datum/supply_pack/science/raw_dimensional_anomaly + name = "Raw Dimensional Anomaly" + desc = "The raw core of a dimensional anomaly, ready to be implosion-compressed into a powerful artifact." + cost = CARGO_CRATE_VALUE * 10 + access = ACCESS_ORDNANCE + access_view = ACCESS_ORDNANCE + contains = list(/obj/item/raw_anomaly_core/dimensional) + crate_name = "raw dimensional anomaly" + crate_type = /obj/structure/closet/crate/secure/science + /datum/supply_pack/science/robotics name = "Robotics Assembly Crate" diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index b7686412e8b..eaed1ec8e48 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -13,6 +13,7 @@ /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, ) if(istype(weapon, /obj/item/assembly/signaler/anomaly)) @@ -454,3 +455,66 @@ qdel(picked_user_part) nearby.update_body(TRUE) balloon_alert(nearby, "something has changed about you") + +// 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", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + playsound(get_turf(owner),'sound/magic/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 + +/** + * Returns a list of all atoms around the source which can be moved away from it. + * + * Arguments + * * source - Thing to try to move things away from. + */ +/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 + +/** + * Pushes something one tile away from the source. + * + * Arguments + * * victim - Thing being moved. + * * source - Thing to move it away from. + */ +/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.safe_throw_at(throwtarget, 1, 1, source, force = MOVE_FORCE_EXTREMELY_STRONG) + +/obj/item/clothing/suit/armor/reactive/barricade/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + 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 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 00000000000..cc60c473864 --- /dev/null +++ b/code/modules/clothing/suits/reactive_armour_dimensional_themes.dm @@ -0,0 +1,190 @@ +#define MAX_BARRIERS 4 +#define MIN_BARRIERS 2 + +/*** + * Datum describing a 'theme' which transforms a 3x3 area's turfs when applied. + * It also creates a number of themed barriers in that area. + */ +/datum/armour_dimensional_theme + var/datum/material/material + var/turf/replace_floor = /turf/open/floor/material + var/turf/replace_wall = /turf/closed/wall/material + var/obj/barricade = /obj/structure/table/greyscale + var/barricade_anchored = TRUE + +/** + * Applies a random dimensional theme effect centered around a provided point. + * This will transform floors and walls, as well as creating some themed barriers in that area. + * + * Arguments + * * source - The central turf to apply outwards from. + * * dangerous - If true this will pick a 'dangerous' theme. + */ +/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) + +/** + * Transforms turfs around a source point into a new themed material. + * Also place some barriers in unoccupied areas. + * + * Arguments + * * source - The central turf to apply outwards from. +*/ +/datum/armour_dimensional_theme/proc/apply(turf/source) + var/obj/effect/particle_effect/fluid/smoke/poof = new(source) + poof.lifetime = 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) + +/** + * Returns a list of turfs which it is valid to apply changes to. + * + * Arguments + * * source - The central turf to check outwards from. + */ +/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 (isspaceturf(check_turf)) + target_area -= check_turf + continue + if (isindestructiblefloor(check_turf)) + target_area -= check_turf + continue + if (isindestructiblewall(check_turf)) + target_area -= check_turf + continue + if (check_turf.holodeck_compatible) + target_area -= check_turf + continue + + return target_area + +/** + * Changes a turf into a different turf + * + * Arguments + * * to_convert - Turf to convert. + */ +/datum/armour_dimensional_theme/proc/convert_turf(turf/to_convert) + if (isfloorturf(to_convert)) + to_convert.ChangeTurf(replace_floor, flags = CHANGETURF_INHERIT_AIR) + else if (iswallturf(to_convert)) + to_convert.ChangeTurf(replace_wall) + + if (material) + var/list/custom_materials = list(GET_MATERIAL_REF(material) = MINERAL_MATERIAL_AMOUNT) + to_convert.set_custom_materials(custom_materials) + +/** + * Places a random amount of themed barriers in unoccupied spaces adjacent to a source tile. + * + * Arguments + * * source - Central tile to place barriers around. + * * target_area - Tiles which have had their materials converted, needs further culling to remove dense tiles. + */ +/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.is_blocked_turf(exclude_mobs = TRUE)) + 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) = MINERAL_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-- + +/** + * Places a barrier at a specified turf. + * + * Arguments + * * source - Tile to place barrier upon. + * * materials - Custom material data to apply to the barrier. + */ +/datum/armour_dimensional_theme/proc/place_barrier(turf/source, list/materials) + var/obj/placed_barricade = new barricade(source) + if (barricade_anchored) + placed_barricade.anchored = TRUE + if (materials.len) + placed_barricade.set_custom_materials(materials) + +/// Themes which will largely be probably useful for the user +/datum/armour_dimensional_theme/safe + +/datum/armour_dimensional_theme/safe/natural + replace_wall = /turf/closed/wall/mineral/wood/nonmetal + replace_floor = /turf/open/floor/grass + barricade = /obj/structure/barricade/wooden + +/datum/armour_dimensional_theme/safe/snow + material = /datum/material/snow + replace_wall = /turf/closed/wall/mineral/snow + replace_floor = /turf/open/floor/fake_snow + barricade = /obj/structure/statue/snow/snowman + +/datum/armour_dimensional_theme/safe/space + material = /datum/material/glass + replace_wall = /turf/closed/wall/rock/porous + replace_floor = /turf/open/floor/fakespace + barricade = /obj/machinery/door/airlock/external/glass/ruin + +/datum/armour_dimensional_theme/safe/glass + material = /datum/material/glass + replace_floor = /turf/open/floor/glass + +/datum/armour_dimensional_theme/safe/secure + replace_wall = /turf/closed/wall/mineral/plastitanium/nodiagonal + replace_floor = /turf/open/floor/mineral/plastitanium + barricade = /obj/structure/holosign/barrier + +/datum/armour_dimensional_theme/safe/meat + material = /datum/material/meat + +/// Dangerous themes can potentially impede the user as much as people pursuing them +/datum/armour_dimensional_theme/dangerous + +/datum/armour_dimensional_theme/dangerous/clown + material = /datum/material/bananium + barricade = /obj/item/restraints/legcuffs/beartrap/prearmed + barricade_anchored = FALSE + +/datum/armour_dimensional_theme/dangerous/radioactive + material = /datum/material/uranium + barricade = /obj/structure/statue/uranium/nuke + +/datum/armour_dimensional_theme/dangerous/plasma + material = /datum/material/plasma + barricade = /obj/structure/statue/plasma/xeno + +/datum/armour_dimensional_theme/dangerous/ice + material = /datum/material/snow + replace_wall = /turf/closed/wall/mineral/snow + replace_floor = /turf/open/floor/fakeice/slippery + barricade = /obj/structure/statue/snow/snowlegion + +/datum/armour_dimensional_theme/dangerous/lavaland + replace_floor = /turf/open/floor/fakebasalt + replace_wall = /turf/closed/wall/mineral/cult + +/// Replace the barrier spawning to instead create weak lava. +/datum/armour_dimensional_theme/dangerous/lavaland/place_barrier(turf/source, list/materials) + source.ChangeTurf(/turf/open/lava/smooth/weak, flags = CHANGETURF_INHERIT_AIR) + +#undef MAX_BARRIERS +#undef MIN_BARRIERS diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 5e8a66fd8d9..57124a1176d 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -172,5 +172,5 @@ /obj/item/wizard_armour_charge name = "battlemage shield charges" desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" + icon = 'icons/effects/anomalies.dmi' + icon_state = "flux" diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index b6390bc1994..e17ecfb59e3 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -10,48 +10,20 @@ /datum/round_event/anomaly var/area/impact_area + var/datum/anomaly_placer/placer = new() var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux announce_when = 1 - -/datum/round_event/anomaly/proc/findEventArea() - var/static/list/allowed_areas - if(!allowed_areas) - //Places that shouldn't explode - var/static/list/safe_area_types = typecacheof(list( - /area/station/ai_monitored/turret_protected/ai, - /area/station/ai_monitored/turret_protected/ai_upload, - /area/station/engineering, - /area/station/solars, - /area/station/holodeck, - /area/shuttle, - /area/station/maintenance, - /area/icemoon, - )) - - //Subtypes from the above that actually should explode. - var/static/list/unsafe_area_subtypes = typecacheof(list(/area/station/engineering/break_room)) - - allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) - if (length(possible_areas)) - return pick(possible_areas) - /datum/round_event/anomaly/setup() - impact_area = findEventArea() - if(!impact_area) - CRASH("No valid areas for anomaly found.") - var/list/turf_test = (get_area_turfs(impact_area) - typesof(/turf/open/lava)) - if(!turf_test.len) - CRASH("Anomaly : No valid turfs found for [impact_area] - [impact_area.type]") + impact_area = placer.findValidArea() /datum/round_event/anomaly/announce(fake) priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") /datum/round_event/anomaly/start() - var/turf/T = pick((get_area_turfs(impact_area) - typesof(/turf/open/lava))) + var/turf/anomaly_turf = placer.findValidTurf(impact_area) var/newAnomaly - if(T) - newAnomaly = new anomaly_path(T) + if(anomaly_turf) + newAnomaly = new anomaly_path(anomaly_turf) if (newAnomaly) announce_to_ghosts(newAnomaly) diff --git a/code/modules/events/anomaly_dimensional.dm b/code/modules/events/anomaly_dimensional.dm new file mode 100644 index 00000000000..30721faa7a0 --- /dev/null +++ b/code/modules/events/anomaly_dimensional.dm @@ -0,0 +1,16 @@ +/datum/round_event_control/anomaly/anomaly_dimensional + name = "Anomaly: Dimensional" + typepath = /datum/round_event/anomaly/anomaly_dimensional + + min_players = 10 + max_occurrences = 5 + weight = 20 + description = "This anomaly replaces the materials of the surrounding area." + +/datum/round_event/anomaly/anomaly_dimensional + start_when = 10 + announce_when = 3 + anomaly_path = /obj/effect/anomaly/dimensional + +/datum/round_event/anomaly/anomaly_dimensional/announce(fake) + priority_announce("Localized dimensional instability detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_placer.dm b/code/modules/events/anomaly_placer.dm new file mode 100644 index 00000000000..90c06d36129 --- /dev/null +++ b/code/modules/events/anomaly_placer.dm @@ -0,0 +1,75 @@ +/datum/anomaly_placer + var/static/list/allowed_areas + +/** + * Returns an area which is safe to place an anomaly inside. + */ +/datum/anomaly_placer/proc/findValidArea() + if(!allowed_areas) + generateAllowedAreas() + var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) + 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 + +/** + * Returns a turf which is safe to place an anomaly on. + * + * Arguments + * * target_area - Area to return a turf from. + */ +/datum/anomaly_placer/proc/findValidTurf(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 location [target_area].") + + return pick(valid_turfs) + +/** + * Returns true if the provided turf is valid to place an anomaly on. + * + * Arguments + * * tested - Turf to try landing on. + */ +/datum/anomaly_placer/proc/is_valid_destination(turf/tested) + if (isspaceturf(tested)) + return FALSE + if (tested.is_blocked_turf(exclude_mobs = TRUE)) + return FALSE + if (islava(tested)) + return FALSE + if (ischasm(tested)) + return FALSE + return TRUE + +/** + * Populates the allowed areas list. + */ +/datum/anomaly_placer/proc/generateAllowedAreas() + //Places that shouldn't explode + var/static/list/safe_area_types = typecacheof(list( + /area/station/ai_monitored/turret_protected/ai, + /area/station/ai_monitored/turret_protected/ai_upload, + /area/station/engineering, + /area/station/solars, + /area/station/holodeck, + /area/shuttle, + /area/station/maintenance, + /area/icemoon, + )) + + //Subtypes from the above that actually should explode. + var/static/list/unsafe_area_subtypes = typecacheof(list(/area/station/engineering/break_room)) + + allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes diff --git a/code/modules/mining/equipment/wormhole_jaunter.dm b/code/modules/mining/equipment/wormhole_jaunter.dm index 4a1a1b445dc..dc70a9d18c9 100644 --- a/code/modules/mining/equipment/wormhole_jaunter.dm +++ b/code/modules/mining/equipment/wormhole_jaunter.dm @@ -96,8 +96,8 @@ //jaunter tunnel /obj/effect/portal/jaunt_tunnel name = "jaunt tunnel" - icon = 'icons/effects/effects.dmi' - icon_state = "bhole3" + icon = 'icons/effects/anomalies.dmi' + icon_state = "vortex" desc = "A stable hole in the universe made by a wormhole jaunter. Turbulent doesn't even begin to describe how rough passage through one of these is, but at least it will always get you somewhere near a beacon." mech_sized = TRUE //save your ripley innate_accuracy_penalty = 6 diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 594bf6712f9..1cb01f65e37 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -415,6 +415,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) new /obj/effect/anomaly/bhole(local_turf, 20, FALSE) if(BIOSCRAMBLER_ANOMALY) new /obj/effect/anomaly/bioscrambler(local_turf, null, FALSE) + if(DIMENSIONAL_ANOMALY) + new /obj/effect/anomaly/dimensional(local_turf, null, FALSE) /obj/machinery/proc/supermatter_zap(atom/zapstart = src, range = 5, zap_str = 4000, zap_flags = ZAP_SUPERMATTER_FLAGS, list/targets_hit = list(), zap_cutoff = 1500, power_level = 0, zap_icon = DEFAULT_ZAP_ICON_STATE, color = null) if(QDELETED(zapstart)) diff --git a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm index 92ff6d7517c..2345d809baf 100644 --- a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm @@ -45,7 +45,7 @@ /// Spawns anomalies all over the station. Half instantly, the other half over time. /datum/sm_delam/proc/effect_anomaly(obj/machinery/power/supermatter_crystal/sm) var/anomalies = 10 - var/list/anomaly_types = list(GRAVITATIONAL_ANOMALY = 55, HALLUCINATION_ANOMALY = 45, DELIMBER_ANOMALY = 35, FLUX_ANOMALY = 25, PYRO_ANOMALY = 5, VORTEX_ANOMALY = 1) + var/list/anomaly_types = list(GRAVITATIONAL_ANOMALY = 55, HALLUCINATION_ANOMALY = 45, DIMENSIONAL_ANOMALY = 35, BIOSCRAMBLER_ANOMALY = 35, FLUX_ANOMALY = 25, PYRO_ANOMALY = 5, VORTEX_ANOMALY = 1) var/list/anomaly_places = GLOB.generic_event_spawns // Spawns this many anomalies instantly. Spawns the rest with callbacks. diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index d9235891374..b293b5993e0 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -73,3 +73,9 @@ 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/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 diff --git a/code/modules/research/anomaly/raw_anomaly.dm b/code/modules/research/anomaly/raw_anomaly.dm index 66d0085b9fe..30dc586c0b1 100644 --- a/code/modules/research/anomaly/raw_anomaly.dm +++ b/code/modules/research/anomaly/raw_anomaly.dm @@ -62,6 +62,12 @@ anomaly_type = /obj/item/assembly/signaler/anomaly/bioscrambler icon_state = "rawcore_bioscrambler" +/obj/item/raw_anomaly_core/dimensional + name = "raw dimensional core" + desc = "The raw core of a dimensional anomaly, vibrating with infinite potential." + anomaly_type = /obj/item/assembly/signaler/anomaly/dimensional + icon_state = "rawcore_dimensional" + /obj/item/raw_anomaly_core/random/Initialize(mapload) . = ..() var/path = pick(subtypesof(/obj/item/raw_anomaly_core)) diff --git a/icons/effects/anomalies.dmi b/icons/effects/anomalies.dmi new file mode 100644 index 00000000000..451a1a8cf49 Binary files /dev/null and b/icons/effects/anomalies.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index dec86a28842..b60dfbf577b 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi index de031383c75..c3635aeb6d9 100644 Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 3f02e62870f..c7d8f76d486 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1525,6 +1525,7 @@ #include "code\game\objects\objs.dm" #include "code\game\objects\structures.dm" #include "code\game\objects\effects\anomalies.dm" +#include "code\game\objects\effects\anomaly_dimensional_themes.dm" #include "code\game\objects\effects\blessing.dm" #include "code\game\objects\effects\bump_teleporter.dm" #include "code\game\objects\effects\contraband.dm" @@ -2875,6 +2876,7 @@ #include "code\modules\clothing\suits\lasertag.dm" #include "code\modules\clothing\suits\moth.dm" #include "code\modules\clothing\suits\reactive_armour.dm" +#include "code\modules\clothing\suits\reactive_armour_dimensional_themes.dm" #include "code\modules\clothing\suits\shirt.dm" #include "code\modules\clothing\suits\straightjacket.dm" #include "code\modules\clothing\suits\toggles.dm" @@ -2929,9 +2931,11 @@ #include "code\modules\events\anomaly.dm" #include "code\modules\events\anomaly_bioscrambler.dm" #include "code\modules\events\anomaly_bluespace.dm" +#include "code\modules\events\anomaly_dimensional.dm" #include "code\modules\events\anomaly_flux.dm" #include "code\modules\events\anomaly_grav.dm" #include "code\modules\events\anomaly_hallucination.dm" +#include "code\modules\events\anomaly_placer.dm" #include "code\modules\events\anomaly_pyro.dm" #include "code\modules\events\anomaly_vortex.dm" #include "code\modules\events\aurora_caelus.dm"