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"