diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm
index 497d8349ce1..9aee8f85eba 100644
--- a/code/__DEFINES/gamemode.dm
+++ b/code/__DEFINES/gamemode.dm
@@ -37,6 +37,7 @@
#define SPECIAL_ROLE_MORPH "Morph"
#define SPECIAL_ROLE_MULTIVERSE "Multiverse Traveller"
#define SPECIAL_ROLE_NUKEOPS "Syndicate"
+#define SPECIAL_ROLE_PYROCLASTIC_SLIME "Pyroclastic Anomaly Slime"
#define SPECIAL_ROLE_RAIDER "Vox Raider"
#define SPECIAL_ROLE_REVENANT "Revenant"
#define SPECIAL_ROLE_SHADOWLING "Shadowling"
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index f302d96347d..aab920f55fd 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -825,3 +825,18 @@ proc/dd_sortedObjectList(list/incoming)
L1[key] += other_value
else
L1[key] = other_value
+
+/**
+ * A proc for turning a list into an associative list.
+ *
+ * A simple proc for turning all things in a list into an associative list, instead
+ * Each item in the list will have an associative value of TRUE
+
+ * Arguments:
+ * * flat_list - the list that it passes to make associative
+ */
+
+/proc/make_associative(list/flat_list)
+ . = list()
+ for(var/thing in flat_list)
+ .[thing] = TRUE
diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm
index 4ea78db674a..01ac7cf8d7d 100644
--- a/code/game/mecha/mecha_construction_paths.dm
+++ b/code/game/mecha/mecha_construction_paths.dm
@@ -1202,99 +1202,99 @@
result = "/obj/mecha/combat/phazon"
steps = list(
//1
- list("key"=/obj/item/assembly/signaler/anomaly,
+ list("key" = /obj/item/assembly/signaler/anomaly/bluespace,
"backkey"=null, //Cannot remove the anomaly core once it's in
"desc"="Anomaly core socket is open and awaiting connection."),
//2
- list("key"=TOOL_WELDER,
+ list("key" = TOOL_WELDER,
"backkey"=TOOL_WRENCH,
"desc"="External armor is wrenched."),
//3
- list("key"=TOOL_WRENCH,
+ list("key" = TOOL_WRENCH,
"backkey"=TOOL_CROWBAR,
"desc"="External armor is installed."),
//4
- list("key"=/obj/item/mecha_parts/part/phazon_armor,
+ list("key" = /obj/item/mecha_parts/part/phazon_armor,
"backkey"=TOOL_WELDER,
"desc"="Phase armor is welded."),
//5
- list("key"=TOOL_WELDER,
+ list("key" = TOOL_WELDER,
"backkey"=TOOL_WRENCH,
"desc"="Phase armor is wrenched."),
//6
- list("key"=TOOL_WRENCH,
+ list("key" = TOOL_WRENCH,
"backkey"=TOOL_CROWBAR,
"desc"="Phase armor is installed."),
//7
- list("key"=/obj/item/stack/sheet/plasteel,
+ list("key" = /obj/item/stack/sheet/plasteel,
"backkey"=TOOL_SCREWDRIVER,
"desc"="The bluespace crystal is engaged."),
//8
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=/obj/item/wirecutters,
"desc"="The bluespace crystal is connected."),
//9
- list("key"=/obj/item/stack/cable_coil,
+ list("key" = /obj/item/stack/cable_coil,
"backkey"=TOOL_CROWBAR,
"desc"="The bluespace crystal is installed."),
//10
- list("key"=/obj/item/stack/ore/bluespace_crystal,
+ list("key" = /obj/item/stack/ore/bluespace_crystal,
"backkey"=TOOL_SCREWDRIVER,
"desc"="Super capacitor is secured."),
//11
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=TOOL_CROWBAR,
"desc"="Super capacitor is installed."),
//12
- list("key"=/obj/item/stock_parts/capacitor/super,
+ list("key" = /obj/item/stock_parts/capacitor/super,
"backkey"=TOOL_SCREWDRIVER,
"desc"="Phasic scanner module is secured."),
//13
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=TOOL_CROWBAR,
"desc"="Phasic scanner module is installed."),
//14
- list("key"=/obj/item/stock_parts/scanning_module/phasic,
+ list("key" = /obj/item/stock_parts/scanning_module/phasic,
"backkey"=TOOL_SCREWDRIVER,
"desc"="Scanning module is secured."),
//15
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=TOOL_CROWBAR,
"desc"="Scanning module is installed."),
//16
- list("key"=/obj/item/circuitboard/mecha/phazon/targeting,
+ list("key" = /obj/item/circuitboard/mecha/phazon/targeting,
"backkey"=TOOL_SCREWDRIVER,
"desc"="Peripherals control module is secured."),
//17
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=TOOL_CROWBAR,
"desc"="Peripherals control module is installed"),
//18
- list("key"=/obj/item/circuitboard/mecha/phazon/peripherals,
+ list("key" = /obj/item/circuitboard/mecha/phazon/peripherals,
"backkey"=TOOL_SCREWDRIVER,
"desc"="Central control module is secured."),
//19
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=TOOL_CROWBAR,
"desc"="Central control module is installed."),
//20
- list("key"=/obj/item/circuitboard/mecha/phazon/main,
+ list("key" = /obj/item/circuitboard/mecha/phazon/main,
"backkey"=TOOL_SCREWDRIVER,
"desc"="The wiring is adjusted."),
//21
- list("key"=/obj/item/wirecutters,
+ list("key" = /obj/item/wirecutters,
"backkey"=TOOL_SCREWDRIVER,
"desc"="The wiring is added."),
//22
- list("key"=/obj/item/stack/cable_coil,
+ list("key" = /obj/item/stack/cable_coil,
"backkey"=TOOL_SCREWDRIVER,
"desc"="The hydraulic systems are active."),
//23
- list("key"=TOOL_SCREWDRIVER,
+ list("key" = TOOL_SCREWDRIVER,
"backkey"=TOOL_WRENCH,
"desc"="The hydraulic systems are connected."),
//24
- list("key"=TOOL_WRENCH,
+ list("key" = TOOL_WRENCH,
"desc"="The hydraulic systems are disconnected.")
)
diff --git a/code/game/mecha/mecha_parts.dm b/code/game/mecha/mecha_parts.dm
index 6b8122e69d9..4bb7f96e66f 100644
--- a/code/game/mecha/mecha_parts.dm
+++ b/code/game/mecha/mecha_parts.dm
@@ -259,6 +259,11 @@
..()
construct = new /datum/construction/mecha/phazon_chassis(src)
+/obj/item/mecha_parts/chassis/phazon/attackby(obj/item/I, mob/user, params)
+ . = ..()
+ if(istype(I, /obj/item/assembly/signaler/anomaly) && !istype(I, /obj/item/assembly/signaler/anomaly/bluespace))
+ to_chat(user, "The anomaly core socket only accepts bluespace anomaly cores!")
+
/obj/item/mecha_parts/part/phazon_torso
name="Phazon Torso"
icon_state = "phazon_harness"
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index ed4f5162447..a1211ec9aa1 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -1,98 +1,180 @@
//Anomalies, used for events. Note that these DO NOT work by themselves; their procs are called by the event datum.
+/// Chance of taking a step per second
+#define ANOMALY_MOVECHANCE 70
+
/obj/effect/anomaly
name = "anomaly"
- icon = 'icons/effects/effects.dmi'
desc = "A mysterious anomaly, seen commonly only in the region of space that the station orbits..."
icon_state = "bhole3"
- density = 0
- anchored = 1
- luminosity = 3
- var/obj/item/assembly/signaler/anomaly/aSignal = null
+ density = FALSE
+ anchored = TRUE
+ light_range = 3
+ var/movechance = ANOMALY_MOVECHANCE
+ var/obj/item/assembly/signaler/anomaly/aSignal = /obj/item/assembly/signaler/anomaly
+ var/area/impact_area
+ /// Time in deciseconds before the anomaly triggers
+ var/lifespan = 990
+ var/death_time
-/obj/effect/anomaly/New()
+ var/countdown_colour
+ var/obj/effect/countdown/anomaly/countdown
+
+ /// Do we drop a core when we're neutralized?
+ var/drops_core = TRUE
+
+/obj/effect/anomaly/Initialize(mapload, new_lifespan, _drops_core = TRUE)
. = ..()
- set_light(initial(luminosity))
- aSignal = new(src)
- aSignal.code = rand(1,100)
-
- var/new_frequency = sanitize_frequency(rand(PUBLIC_LOW_FREQ, PUBLIC_HIGH_FREQ))
- aSignal.set_frequency(new_frequency)
GLOB.poi_list |= src
+ START_PROCESSING(SSobj, src)
+ impact_area = get_area(src)
+
+ if(!impact_area)
+ return INITIALIZE_HINT_QDEL
+
+ drops_core = _drops_core
+
+ aSignal = new aSignal(src)
+ aSignal.code = rand(1, 100)
+ aSignal.anomaly_type = type
+
+ var/frequency = rand(PUBLIC_LOW_FREQ, PUBLIC_HIGH_FREQ)
+ if(ISMULTIPLE(frequency, 2))//signaller frequencies are always uneven!
+ frequency++
+ aSignal.set_frequency(frequency)
+
+ if(new_lifespan)
+ lifespan = new_lifespan
+ death_time = world.time + lifespan
+ countdown = new(src)
+ if(countdown_colour)
+ countdown.color = countdown_colour
+ countdown.start()
/obj/effect/anomaly/Destroy()
- QDEL_NULL(aSignal)
GLOB.poi_list.Remove(src)
+ STOP_PROCESSING(SSobj, src)
+ QDEL_NULL(countdown)
+ QDEL_NULL(aSignal)
return ..()
+/obj/effect/anomaly/process()
+ anomalyEffect()
+ if(death_time < world.time)
+ if(loc)
+ detonate()
+ qdel(src)
+
/obj/effect/anomaly/proc/anomalyEffect()
- if(prob(50))
+ if(prob(movechance))
step(src, pick(GLOB.alldirs))
+/obj/effect/anomaly/proc/detonate()
+ return
+
+/obj/effect/anomaly/ex_act(severity)
+ if(severity == EXPLODE_DEVASTATE)
+ qdel(src)
/obj/effect/anomaly/proc/anomalyNeutralize()
- var/turf/T = get_turf(src)
+ new /obj/effect/particle_effect/smoke/bad(loc)
- new /obj/effect/particle_effect/smoke/bad(T)
-
- if(aSignal)
- aSignal.forceMove(T)
+ if(drops_core)
+ aSignal.forceMove(drop_location())
aSignal = null
+ // else, anomaly core gets deleted by qdel(src).
qdel(src)
/obj/effect/anomaly/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/analyzer))
- to_chat(user, "Analyzing... [src]'s unstable field is fluctuating along frequency [aSignal.code]:[format_frequency(aSignal.frequency)].")
+ to_chat(user, "Analyzing... [src]'s unstable field is fluctuating along frequency [format_frequency(aSignal.frequency)], code [aSignal.code].")
///////////////////////
/obj/effect/anomaly/grav
name = "gravitational anomaly"
icon_state = "shield2"
- density = 1
- var/boing = 0
-
-/obj/effect/anomaly/grav/New()
- ..()
- aSignal.origin_tech = "magnets=7"
+ density = FALSE
+ var/boing = FALSE
+ aSignal = /obj/item/assembly/signaler/anomaly/grav
/obj/effect/anomaly/grav/anomalyEffect()
..()
-
- boing = 1
+ 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))
+ gravShock(M)
for(var/mob/living/M in orange(4, src))
- step_towards(M,src)
+ if(!M.mob_negates_gravity())
+ step_towards(M,src)
+ for(var/obj/O in range(0, src))
+ if(!O.anchored)
+ var/mob/living/target = locate() in view(4, src)
+ if(target && !target.stat)
+ O.throw_at(target, 5, 10)
-/obj/effect/anomaly/grav/Bump(mob/A)
+/obj/effect/anomaly/grav/Crossed(atom/movable/AM)
+ . = ..()
+ gravShock(AM)
+
+/obj/effect/anomaly/grav/Bump(atom/A)
gravShock(A)
- return
-/obj/effect/anomaly/grav/Bumped(mob/A)
- gravShock(A)
- return
+/obj/effect/anomaly/grav/Bumped(atom/movable/AM)
+ gravShock(AM)
-/obj/effect/anomaly/grav/proc/gravShock(var/mob/A)
+/obj/effect/anomaly/grav/proc/gravShock(mob/living/A)
if(boing && isliving(A) && !A.stat)
A.Weaken(2)
var/atom/target = get_edge_target_turf(A, get_dir(src, get_step_away(A, src)))
A.throw_at(target, 5, 1)
- boing = 0
- return
+ boing = FALSE
/////////////////////
/obj/effect/anomaly/flux
name = "flux wave anomaly"
icon_state = "electricity2"
+ density = TRUE
+ aSignal = /obj/item/assembly/signaler/anomaly/flux
+ var/canshock = FALSE
+ var/shockdamage = 20
+ var/explosive = TRUE
-/obj/effect/anomaly/flux/New()
+/obj/effect/anomaly/flux/Initialize(mapload, new_lifespan, drops_core = TRUE, _explosive = TRUE)
+ . = ..()
+ explosive = _explosive
+
+/obj/effect/anomaly/flux/anomalyEffect()
..()
- aSignal.origin_tech = "powerstorage=7"
+ canshock = TRUE
+ for(var/mob/living/M in get_turf(src))
+ mobShock(M)
+
+/obj/effect/anomaly/flux/Crossed(atom/movable/AM)
+ . = ..()
+ 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/proc/mobShock(mob/living/M)
+ if(canshock && istype(M))
+ canshock = FALSE //Just so you don't instakill yourself if you slam into the anomaly five times in a second.
+ M.electrocute_act(shockdamage, "[name]", safety = TRUE)
+
+/obj/effect/anomaly/flux/detonate()
+ if(explosive)
+ explosion(src, 1, 4, 16, 18) //Low devastation, but hits a lot of stuff.
+ else
+ new /obj/effect/particle_effect/sparks(loc)
/////////////////////
@@ -100,32 +182,116 @@
name = "bluespace anomaly"
icon = 'icons/obj/projectiles.dmi'
icon_state = "bluespace"
- density = 1
+ density = TRUE
+ aSignal = /obj/item/assembly/signaler/anomaly/bluespace
-/obj/effect/anomaly/bluespace/New()
+/obj/effect/anomaly/bluespace/anomalyEffect()
..()
- aSignal.origin_tech = "bluespace=7"
+ for(var/mob/living/M in range(1, src))
+ do_teleport(M, locate(M.x, M.y, M.z), 4)
+
+/obj/effect/anomaly/bluespace/Bumped(atom/movable/AM)
+ if(isliving(AM))
+ do_teleport(AM, locate(AM.x, AM.y, AM.z), 8)
+
+/obj/effect/anomaly/bluespace/detonate()
+ var/turf/T = pick(get_area_turfs(impact_area))
+ if(T)
+ // Calculate new position (searches through beacons in world)
+ var/obj/item/radio/beacon/chosen
+ var/list/possible = list()
+ for(var/obj/item/radio/beacon/W in GLOB.beacons)
+ if(!is_station_level(W.z))
+ continue
+ possible += W
+
+ if(length(possible))
+ chosen = pick(possible)
+
+ if(chosen)
+ // Calculate previous position for transition
+ var/turf/turf_from = T // the turf of origin we're travelling FROM
+ var/turf/turf_to = get_turf(chosen) // the turf of origin we're travelling TO
+
+ playsound(turf_to, 'sound/effects/phasein.ogg', 100, TRUE)
+ GLOB.event_announcement.Announce("Massive bluespace translocation detected.", "Anomaly Alert")
+
+ var/list/flashers = list()
+ for(var/mob/living/carbon/C in viewers(turf_to, null))
+ if(C.flash_eyes())
+ flashers += C
+
+ var/y_distance = turf_to.y - turf_from.y
+ var/x_distance = turf_to.x - turf_from.x
+ for(var/atom/movable/A in urange(12, turf_from)) // iterate thru list of mobs in the area
+ if(istype(A, /obj/item/radio/beacon))
+ continue // don't teleport beacons because that's just insanely stupid
+ if(A.anchored || A.move_resist == INFINITY)
+ continue
+
+ var/turf/newloc = locate(A.x + x_distance, A.y + y_distance, turf_to.z) // calculate the new place
+ if(!A.Move(newloc) && newloc) // if the atom, for some reason, can't move, FORCE them to move! :) We try Move() first to invoke any movement-related checks the atom needs to perform after moving
+ A.forceMove(newloc)
+
+ if(ismob(A) && !(A in flashers)) // don't flash if we're already doing an effect
+ var/mob/M = A
+ if(M.client)
+ INVOKE_ASYNC(src, .proc/blue_effect, M)
+
+/obj/effect/anomaly/bluespace/proc/blue_effect(mob/M)
+ var/obj/blueeffect = new /obj(src)
+ blueeffect.screen_loc = "WEST,SOUTH to EAST,NORTH"
+ blueeffect.icon = 'icons/effects/effects.dmi'
+ blueeffect.icon_state = "shieldsparkles"
+ blueeffect.layer = FLASH_LAYER
+ blueeffect.plane = FULLSCREEN_PLANE
+ blueeffect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ M.client.screen += blueeffect
+ sleep(20)
+ M.client.screen -= blueeffect
+ qdel(blueeffect)
-/obj/effect/anomaly/bluespace/Bumped(atom/A)
- if(isliving(A))
- do_teleport(A, locate(A.x, A.y, A.z), 10)
- return
/////////////////////
/obj/effect/anomaly/pyro
name = "pyroclastic anomaly"
icon_state = "mustard"
-
-/obj/effect/anomaly/pyro/New()
- ..()
- aSignal.origin_tech = "plasmatech=7"
+ var/ticks = 0
+ aSignal = /obj/item/assembly/signaler/anomaly/pyro
/obj/effect/anomaly/pyro/anomalyEffect()
..()
+ ticks++
+ if(ticks < 5)
+ return
+ else
+ ticks = 0
var/turf/simulated/T = get_turf(src)
if(istype(T))
- T.atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 3)
+ T.atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS | LINDA_SPAWN_OXYGEN, 5)
+
+/obj/effect/anomaly/pyro/detonate()
+ INVOKE_ASYNC(src, .proc/makepyroslime)
+
+/obj/effect/anomaly/pyro/proc/makepyroslime()
+ var/turf/simulated/T = get_turf(src)
+ if(istype(T))
+ T.atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS | LINDA_SPAWN_OXYGEN, 500) //Make it hot and burny for the new slime
+ var/new_colour = pick("red", "orange")
+ var/mob/living/simple_animal/slime/S = new(T, new_colour)
+ S.rabid = TRUE
+ S.amount_grown = SLIME_EVOLUTION_THRESHOLD
+ S.Evolve()
+ var/datum/action/innate/slime/reproduce/A = new
+ A.Grant(S)
+
+ var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a pyroclastic anomaly slime?", ROLE_SENTIENT, FALSE, 100, source = S, role_cleanname = "pyroclastic anomaly slime")
+ if(LAZYLEN(candidates))
+ var/mob/dead/observer/chosen = pick(candidates)
+ S.key = chosen.key
+ S.mind.special_role = SPECIAL_ROLE_PYROCLASTIC_SLIME
+ log_game("[key_name(S.key)] was made into a slime by pyroclastic anomaly at [AREACOORD(T)].")
/////////////////////
@@ -133,10 +299,7 @@
name = "vortex anomaly"
icon_state = "bhole3"
desc = "That's a nice station you have there. It'd be a shame if something happened to it."
-
-/obj/effect/anomaly/bhole/New()
- ..()
- aSignal.origin_tech = "engineering=7"
+ aSignal = /obj/item/assembly/signaler/anomaly/vortex
/obj/effect/anomaly/bhole/anomalyEffect()
..()
@@ -144,31 +307,31 @@
qdel(src)
return
- grav(rand(0,3), rand(2,3), 50, 25)
+ grav(rand(0, 3), rand(2, 3), 50, 25)
//Throwing stuff around!
- for(var/obj/O in orange(1,src))
+ for(var/obj/O in range(2, src))
+ if(O == src)
+ return //DON'T DELETE YOURSELF GOD DAMN
if(!O.anchored)
- var/mob/living/target = locate() in view(5,src)
- if(!target)
- return
- O.throw_at(target, 5, 10)
- return
+ var/mob/living/target = locate() in view(4, src)
+ if(target && !target.stat)
+ O.throw_at(target, 7, 5)
else
- O.ex_act(2)
+ O.ex_act(EXPLODE_HEAVY)
-/obj/effect/anomaly/bhole/proc/grav(var/r, var/ex_act_force, var/pull_chance, var/turf_removal_chance)
+/obj/effect/anomaly/bhole/proc/grav(r, ex_act_force, pull_chance, turf_removal_chance)
for(var/t = -r, t < r, t++)
- affect_coord(x+t, y-r, ex_act_force, pull_chance, turf_removal_chance)
- affect_coord(x-t, y+r, ex_act_force, pull_chance, turf_removal_chance)
- affect_coord(x+r, y+t, ex_act_force, pull_chance, turf_removal_chance)
- affect_coord(x-r, y-t, ex_act_force, pull_chance, turf_removal_chance)
- return
+ affect_coord(x + t, y - r, ex_act_force, pull_chance, turf_removal_chance)
+ affect_coord(x - t, y + r, ex_act_force, pull_chance, turf_removal_chance)
+ affect_coord(x + r, y + t, ex_act_force, pull_chance, turf_removal_chance)
+ affect_coord(x - r, y - t, ex_act_force, pull_chance, turf_removal_chance)
-/obj/effect/anomaly/bhole/proc/affect_coord(var/x, var/y, var/ex_act_force, var/pull_chance, var/turf_removal_chance)
+/obj/effect/anomaly/bhole/proc/affect_coord(x, y, ex_act_force, pull_chance, turf_removal_chance)
//Get turf at coordinate
var/turf/T = locate(x, y, z)
- if(isnull(T)) return
+ if(isnull(T))
+ return
//Pulling and/or ex_act-ing movable atoms in that turf
if(prob(pull_chance))
@@ -176,38 +339,12 @@
if(O.anchored)
O.ex_act(ex_act_force)
else
- step_towards(O,src)
+ step_towards(O, src)
for(var/mob/living/M in T.contents)
- step_towards(M,src)
+ step_towards(M, src)
//Damaging the turf
- if( T && istype(T,/turf/simulated) && prob(turf_removal_chance) )
+ if(T && prob(turf_removal_chance))
T.ex_act(ex_act_force)
- return
-/////////////////////
-
-/obj/effect/anomaly/atmos
- name = "transformative gas anomaly"
- icon_state = "electricity2"
- var/gas_type
-
-/obj/effect/anomaly/atmos/New()
- ..()
- gas_type = pick(GAS_N2O, GAS_CO2, GAS_N2)
- aSignal.origin_tech = "materials=7" //turning gas into another gas has some interesting implications for material science
- //might also work as biotech maybe? Since there's no anomaly for that.
-
-/obj/effect/anomaly/atmos/anomalyEffect()
- ..()
- var/turf/simulated/T = get_turf(src)
- if(istype(T))
- var/flag
- switch(gas_type)
- if(GAS_CO2)
- flag = LINDA_SPAWN_CO2
- if(GAS_N2O)
- flag = LINDA_SPAWN_N2O
- else
- flag = LINDA_SPAWN_NITROGEN
- T.atmos_spawn_air(LINDA_SPAWN_20C | flag, 10)
+#undef ANOMALY_MOVECHANCE
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 0c3fcfec3ac..c4a7d7b59ec 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -151,20 +151,3 @@
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
-
-// Embedded signaller used in anomalies.
-/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"
- item_state = "electronic"
- resistance_flags = FIRE_PROOF
- receiving = TRUE
-
-/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
- if(..())
- for(var/obj/effect/anomaly/A in orange(0, src))
- A.anomalyNeutralize()
-
-/obj/item/assembly/signaler/anomaly/attack_self()
- return
diff --git a/code/modules/countdown/countdown.dm b/code/modules/countdown/countdown.dm
index 714590c5b23..cbac113f32a 100644
--- a/code/modules/countdown/countdown.dm
+++ b/code/modules/countdown/countdown.dm
@@ -5,22 +5,22 @@
And maybe we'll come back\n\
To earth, who can tell?"
- var/displayed_text
- var/atom/attached_to
- color = "#ff0000"
- var/text_size = 4
- var/started = FALSE
invisibility = INVISIBILITY_OBSERVER
anchored = TRUE
- layer = GHOST_LAYER
+ layer = MASSIVE_OBJ_LAYER
+ color = "#ff0000" // text color
+ 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
-/obj/effect/countdown/New(atom/A)
+/obj/effect/countdown/Initialize(mapload)
. = ..()
- attach(A)
+ attach(loc)
/obj/effect/countdown/examine(mob/user)
. = ..()
- . += "This countdown is displaying: [displayed_text]"
+ . += "This countdown is displaying: [displayed_text]."
/obj/effect/countdown/proc/attach(atom/A)
attached_to = A
@@ -51,7 +51,7 @@
displayed_text = new_val
if(displayed_text)
- maptext = "[displayed_text]"
+ maptext = "[displayed_text]"
else
maptext = null
@@ -90,3 +90,13 @@
else if(C.occupant)
var/completion = round(C.get_completion())
return completion
+
+/obj/effect/countdown/anomaly
+ name = "anomaly countdown"
+
+/obj/effect/countdown/anomaly/get_value()
+ var/obj/effect/anomaly/A = attached_to
+ if(!istype(A))
+ return
+ var/time_left = max(0, (A.death_time - world.time) / 10)
+ return round(time_left)
diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm
index 68edfb68200..c806fac3ce0 100644
--- a/code/modules/events/anomaly.dm
+++ b/code/modules/events/anomaly.dm
@@ -1,33 +1,47 @@
/datum/event/anomaly
- var/obj/effect/anomaly/newAnomaly
+ name = "Anomaly: Energetic Flux"
+ var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux
announceWhen = 1
-/datum/event/anomaly/setup(loop=0)
- var/safety_loop = loop + 1
- if(safety_loop > 50)
- kill()
- end()
+/datum/event/anomaly/proc/findEventArea()
+ var/static/list/allowed_areas
+ if(!allowed_areas)
+ //Places that shouldn't explode
+ var/list/safe_area_types = typecacheof(list(
+ /area/turret_protected/ai,
+ /area/turret_protected/ai_upload,
+ /area/ai_monitored/storage/emergency,
+ /area/engine,
+ /area/solar,
+ /area/holodeck,
+ /area/shuttle,
+ /area/maintenance,
+ /area/toxins/test_area)
+ )
+
+ //Subtypes from the above that actually should explode.
+ var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room))
+
+ allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes
+ var/list/possible_areas = typecache_filter_list(GLOB.all_areas, allowed_areas)
+ if(length(possible_areas))
+ return pick(possible_areas)
+
+/datum/event/anomaly/setup()
impact_area = findEventArea()
if(!impact_area)
- setup(safety_loop)
+ CRASH("No valid areas for anomaly found.")
var/list/turf_test = get_area_turfs(impact_area)
- if(!turf_test.len)
- setup(safety_loop)
+ if(!length(turf_test))
+ CRASH("Anomaly: No valid turfs found for [impact_area] - [impact_area.type]")
/datum/event/anomaly/announce()
GLOB.event_announcement.Announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert")
/datum/event/anomaly/start()
var/turf/T = pick(get_area_turfs(impact_area))
+ var/newAnomaly
if(T)
- newAnomaly = new /obj/effect/anomaly/flux(T.loc)
-
-/datum/event/anomaly/tick()
- if(!newAnomaly)
- kill()
- return
- newAnomaly.anomalyEffect()
-
-/datum/event/anomaly/end()
- if(newAnomaly)//Kill the anomaly if it still exists at the end.
- qdel(newAnomaly)
+ newAnomaly = new anomaly_path(T)
+ if(newAnomaly)
+ announce_to_ghosts(newAnomaly)
diff --git a/code/modules/events/anomaly_atmos.dm b/code/modules/events/anomaly_atmos.dm
deleted file mode 100644
index fa512cb522b..00000000000
--- a/code/modules/events/anomaly_atmos.dm
+++ /dev/null
@@ -1,52 +0,0 @@
-/datum/event/anomaly/anomaly_atmos
- startWhen = 30
- announceWhen = 3
- endWhen = 100
-
-/datum/event/anomaly/anomaly_atmos/announce()
- GLOB.event_announcement.Announce("Transformative gas anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")
-
-/datum/event/anomaly/anomaly_atmos/start()
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- newAnomaly = new /obj/effect/anomaly/atmos(T.loc)
-
-
-/datum/event/anomaly/anomaly_atmos/tick()
- if(!newAnomaly)
- kill()
- return
- if(ISMULTIPLE(activeFor, 5))
- newAnomaly.anomalyEffect()
-
-/datum/event/anomaly/anomaly_atmos/end()
- if(!newAnomaly || !newAnomaly.loc) //no anomaly or it is in nullspace
- return
- var/obj/effect/anomaly/atmos/A = newAnomaly
- var/gas_type = A.gas_type
- var/area/t_area = get_area(A)
- for(var/turf/T in t_area)
- if(istype(T, /turf/simulated)) //should not occur on unsimulated turfs without admemery
- var/turf/simulated/S = T
- fill_with_gas(gas_type, S)
- explosion(get_turf(newAnomaly), -1, 0, 2) // a small boom so people know something happened.
- ..()
-
-/**
- * Replaces all oxygen and nitrogen on the simulated turf S with the gas type specified in gas, taking N2, N20, CO2 and agent B
- */
-/datum/event/anomaly/anomaly_atmos/proc/fill_with_gas(gas, turf/simulated/S)
- if(!S.air)//no air to transform
- return
- var/amount_to_add = S.air.oxygen + S.air.nitrogen
- S.air.oxygen = 0
- S.air.nitrogen = 0
- switch(gas)
- if(GAS_N2)
- S.air.nitrogen += amount_to_add
- if(GAS_N2O)
- S.air.sleeping_agent += amount_to_add
- if(GAS_CO2)
- S.air.carbon_dioxide += amount_to_add
- S.update_visuals()
- S.air_update_turf()
diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm
index 929612cf6f9..b90e8b86b01 100644
--- a/code/modules/events/anomaly_bluespace.dm
+++ b/code/modules/events/anomaly_bluespace.dm
@@ -1,69 +1,8 @@
/datum/event/anomaly/anomaly_bluespace
+ name = "Anomaly: Bluespace"
startWhen = 3
announceWhen = 10
- endWhen = 160
+ anomaly_path = /obj/effect/anomaly/bluespace
/datum/event/anomaly/anomaly_bluespace/announce()
GLOB.event_announcement.Announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")
-
-/datum/event/anomaly/anomaly_bluespace/start()
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- newAnomaly = new /obj/effect/anomaly/bluespace(T.loc)
-
-/datum/event/anomaly/anomaly_bluespace/end()
- if(newAnomaly)//If it hasn't been neutralized, it's time to warp half the station away jeez
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- // Calculate new position (searches through beacons in world)
- var/obj/item/radio/beacon/chosen
- var/list/possible = list()
- for(var/obj/item/radio/beacon/W in GLOB.global_radios)
- if(!is_station_level(W.z))
- continue
- possible += W
-
- if(possible.len > 0)
- chosen = pick(possible)
-
- if(chosen)
- // Calculate previous position for transition
-
- var/turf/FROM = T // the turf of origin we're travelling FROM
- var/turf/TO = get_turf(chosen) // the turf of origin we're travelling TO
-
- playsound(TO, 'sound/effects/phasein.ogg', 100, 1)
- GLOB.event_announcement.Announce("Massive bluespace translocation detected.", "Anomaly Alert")
-
- var/list/flashers = list()
- for(var/mob/living/carbon/C in viewers(TO, null))
- if(C.flash_eyes())
- flashers += C
-
- var/y_distance = TO.y - FROM.y
- var/x_distance = TO.x - FROM.x
- for(var/atom/movable/A in range(12, FROM )) // iterate thru list of mobs in the area
- if(istype(A, /obj/item/radio/beacon)) // don't teleport beacons because that's just insanely stupid
- continue
- if(A.anchored || A.move_resist == INFINITY)
- continue
- var/turf/newloc = locate(A.x + x_distance, A.y + y_distance, TO.z) // calculate the new place
- if(!A.Move(newloc)) // if the atom, for some reason, can't move, FORCE them to move! :) We try Move() first to invoke any movement-related checks the atom needs to perform after moving
- A.loc = locate(A.x + x_distance, A.y + y_distance, TO.z)
-
- spawn()
- if(ismob(A) && !(A in flashers)) // don't flash if we're already doing an effect
- var/mob/M = A
- if(M.client)
- var/obj/blueeffect = new /obj(src)
- blueeffect.screen_loc = "WEST,SOUTH to EAST,NORTH"
- blueeffect.icon = 'icons/effects/effects.dmi'
- blueeffect.icon_state = "shieldsparkles"
- blueeffect.layer = FLASH_LAYER
- blueeffect.plane = FULLSCREEN_PLANE
- blueeffect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- M.client.screen += blueeffect
- sleep(20)
- M.client.screen -= blueeffect
- qdel(blueeffect)
- qdel(newAnomaly)
diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm
index 714ae1e9d19..350e21cb496 100644
--- a/code/modules/events/anomaly_flux.dm
+++ b/code/modules/events/anomaly_flux.dm
@@ -1,17 +1,8 @@
/datum/event/anomaly/anomaly_flux
- startWhen = 3
- announceWhen = 20
- endWhen = 180
+ name = "Anomaly: Hyper-Energetic Flux"
+ startWhen = 10
+ announceWhen = 3
+ anomaly_path = /obj/effect/anomaly/flux
/datum/event/anomaly/anomaly_flux/announce()
GLOB.event_announcement.Announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")
-
-/datum/event/anomaly/anomaly_flux/start()
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- newAnomaly = new /obj/effect/anomaly/flux(T.loc)
-
-/datum/event/anomaly/anomaly_flux/end()
- if(newAnomaly.loc)//If it hasn't been neutralized, it's time to blow up.
- explosion(newAnomaly, -1, 3, 5, 5)
- qdel(newAnomaly)
diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm
index 03b9c2247f6..4acf530c081 100644
--- a/code/modules/events/anomaly_grav.dm
+++ b/code/modules/events/anomaly_grav.dm
@@ -1,12 +1,8 @@
/datum/event/anomaly/anomaly_grav
+ name = "Anomaly: Gravitational"
startWhen = 3
announceWhen = 20
- endWhen = 70
+ anomaly_path = /obj/effect/anomaly/grav
/datum/event/anomaly/anomaly_grav/announce()
GLOB.event_announcement.Announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")
-
-/datum/event/anomaly/anomaly_grav/start()
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- newAnomaly = new /obj/effect/anomaly/grav(T.loc)
diff --git a/code/modules/events/anomaly_pyro.dm b/code/modules/events/anomaly_pyro.dm
index 0a5dc14bc3a..2caa8dc4a0c 100644
--- a/code/modules/events/anomaly_pyro.dm
+++ b/code/modules/events/anomaly_pyro.dm
@@ -1,31 +1,8 @@
/datum/event/anomaly/anomaly_pyro
- startWhen = 30
- announceWhen = 3
- endWhen = 110
+ name = "Anomaly: Pyroclastic"
+ startWhen = 3
+ announceWhen = 10
+ anomaly_path = /obj/effect/anomaly/pyro
/datum/event/anomaly/anomaly_pyro/announce()
GLOB.event_announcement.Announce("Atmospheric anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert")
-
-/datum/event/anomaly/anomaly_pyro/start()
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- newAnomaly = new /obj/effect/anomaly/pyro(T.loc)
-
-/datum/event/anomaly/anomaly_pyro/tick()
- if(!newAnomaly)
- kill()
- return
- if(ISMULTIPLE(activeFor, 5))
- newAnomaly.anomalyEffect()
-
-/datum/event/anomaly/anomaly_pyro/end()
- if(newAnomaly.loc)
- explosion(get_turf(newAnomaly), -1,0,3, flame_range = 4)
-
- var/turf/T = get_turf(src)
- var/new_colour = pick("red", "orange")
- var/mob/living/simple_animal/slime/S = new(T, new_colour)
- S.rabid = TRUE
- S.amount_grown = SLIME_EVOLUTION_THRESHOLD
- S.Evolve()
- qdel(newAnomaly)
diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm
index 76a952dba12..76058cfa574 100644
--- a/code/modules/events/anomaly_vortex.dm
+++ b/code/modules/events/anomaly_vortex.dm
@@ -1,12 +1,8 @@
/datum/event/anomaly/anomaly_vortex
- startWhen = 20
+ name = "Anomaly: Vortex"
+ startWhen = 10
announceWhen = 3
- endWhen = 80
+ anomaly_path = /obj/effect/anomaly/bhole
/datum/event/anomaly/anomaly_vortex/announce()
GLOB.event_announcement.Announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert")
-
-/datum/event/anomaly/anomaly_vortex/start()
- var/turf/T = pick(get_area_turfs(impact_area))
- if(T)
- newAnomaly = new /obj/effect/anomaly/bhole(T.loc)
diff --git a/code/modules/events/event.dm b/code/modules/events/event.dm
index d7fa555ca5e..dc6016d2c1f 100644
--- a/code/modules/events/event.dm
+++ b/code/modules/events/event.dm
@@ -51,6 +51,8 @@
return 0*/
/datum/event //NOTE: Times are measured in master controller ticks!
+ /// The human-readable name of the event
+ var/name
var/processing = 1
/// When in the lifetime to call start().
var/startWhen = 0
@@ -190,3 +192,10 @@
setup()
..()
+
+//Called after something followable has been spawned by an event
+//Provides ghosts a follow link to an atom if possible
+//Only called once.
+/datum/event/proc/announce_to_ghosts(atom/atom_of_interest)
+ if(atom_of_interest)
+ notify_ghosts("[name] has an object of interest: [atom_of_interest]!", title = "Something's Interesting!", source = atom_of_interest, action = NOTIFY_FOLLOW)
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 6bb7394fdb4..6eee71921b9 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -174,7 +174,6 @@ GLOBAL_LIST_EMPTY(event_last_fired)
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Vent Clog", /datum/event/vent_clog, 250),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Wormholes", /datum/event/wormholes, 150),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Pyro Anomaly", /datum/event/anomaly/anomaly_pyro, 75, list(ASSIGNMENT_ENGINEER = 60)),
- new /datum/event_meta(EVENT_LEVEL_MODERATE, "Atmos Anomaly", /datum/event/anomaly/anomaly_atmos, 75, list(ASSIGNMENT_ENGINEER = 60)),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Vortex Anomaly", /datum/event/anomaly/anomaly_vortex, 75, list(ASSIGNMENT_ENGINEER = 25)),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Bluespace Anomaly", /datum/event/anomaly/anomaly_bluespace, 75, list(ASSIGNMENT_ENGINEER = 25)),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Flux Anomaly", /datum/event/anomaly/anomaly_flux, 75, list(ASSIGNMENT_ENGINEER = 50)),
diff --git a/code/modules/research/anomaly/anomaly.dm b/code/modules/research/anomaly/anomaly.dm
new file mode 100644
index 00000000000..c8401b6ed99
--- /dev/null
+++ b/code/modules/research/anomaly/anomaly.dm
@@ -0,0 +1,53 @@
+// Embedded signaller used in anomalies.
+/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"
+ item_state = "electronic"
+ resistance_flags = FIRE_PROOF
+ receiving = TRUE
+ var/anomaly_type = /obj/effect/anomaly
+
+/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
+ if(..())
+ for(var/obj/effect/anomaly/A in get_turf(src))
+ A.anomalyNeutralize()
+
+/obj/item/assembly/signaler/anomaly/attack_self()
+ return
+
+//Anomaly cores
+/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
+ origin_tech = "plasmatech=7"
+
+/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
+ origin_tech = "magnets=7"
+
+/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
+ origin_tech = "powerstorage=7"
+
+/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
+ origin_tech = "bluespace=7"
+
+/obj/item/assembly/signaler/anomaly/vortex
+ name = "\improper vortex anomaly core"
+ desc = "The neutralized core of a vortex anomaly. It won't sit still, as if some invisible force is acting on it. It'd probably be valuable for research."
+ icon_state = "vortex_core"
+ anomaly_type = /obj/effect/anomaly/bhole
+ origin_tech = "engineering=7"
diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi
index 89b665cd635..971a42e6143 100644
Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 2e7219ff97d..49361d16747 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1465,7 +1465,6 @@
#include "code\modules\events\abductor.dm"
#include "code\modules\events\alien_infestation.dm"
#include "code\modules\events\anomaly.dm"
-#include "code\modules\events\anomaly_atmos.dm"
#include "code\modules\events\anomaly_bluespace.dm"
#include "code\modules\events\anomaly_flux.dm"
#include "code\modules\events\anomaly_grav.dm"
@@ -2312,6 +2311,7 @@
#include "code\modules\research\rdmachines.dm"
#include "code\modules\research\research.dm"
#include "code\modules\research\server.dm"
+#include "code\modules\research\anomaly\anomaly.dm"
#include "code\modules\research\designs\AI_module_designs.dm"
#include "code\modules\research\designs\autolathe_designs.dm"
#include "code\modules\research\designs\biogenerator_designs.dm"