Ports TG Anomalies (#18794)

* Anomalies

* Tiles

* Automatic changelog compile [ci skip]

* Bioscramble

* Reactive armour

* Reactive

* .

* Automatic changelog compile [ci skip]

* hallucination

* Snowy

* Bit of polishing

* change

* Cargo & Research

* fimx

* Anomaly stuffs

* Bioscrambler + Terrible fire armour

* Adjustment

* Less spammy

* Changes

---------

Co-authored-by: vorestation-ci[bot] <199609141+vorestation-ci[bot]@users.noreply.github.com>
This commit is contained in:
Guti
2025-12-11 06:47:48 -05:00
committed by GitHub
co-authored by vorestation-ci[bot] <199609141+vorestation-ci[bot]@users.noreply.github.com>
parent 81acf7df5c
commit d59ad9df0c
48 changed files with 1859 additions and 13 deletions
@@ -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)
@@ -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()
@@ -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
@@ -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
@@ -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/)
)
@@ -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 ..()
@@ -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
@@ -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
@@ -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()
@@ -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
@@ -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)
+2 -3
View File
@@ -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)
*/
@@ -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
@@ -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