diff --git a/code/__defines/anomaly.dm b/code/__defines/anomaly.dm index ef3db08b37..03ccb13e0f 100644 --- a/code/__defines/anomaly.dm +++ b/code/__defines/anomaly.dm @@ -47,3 +47,19 @@ #define DIMENSIONAL_ANOMALY "dimensional_anomaly" #define WEATHER_ANOMALY "weather_anomaly" #define DUST_ANOMALY "dust_anomaly" + +#define ANOMALY_PARTICLE_SIGMA "Sigma particles" +#define ANOMALY_PARTICLE_DELTA "Delta particles" +#define ANOMALY_PARTICLE_ZETA "Zeta particles" +#define ANOMALY_PARTICLE_EPSILON "Epsilon particles" + +#define ANOMALY_PARTICLE_ALL list( \ + ANOMALY_PARTICLE_SIGMA, \ + ANOMALY_PARTICLE_DELTA, \ + ANOMALY_PARTICLE_ZETA, \ + ANOMALY_PARTICLE_EPSILON \ +) + +#define ANOMALY_DECAYING "Decaying" +#define ANOMALY_STABLE "Stable" +#define ANOMALY_GROWING "Growing" diff --git a/code/__defines/research.dm b/code/__defines/research.dm index 15aee3327d..778afa580f 100644 --- a/code/__defines/research.dm +++ b/code/__defines/research.dm @@ -35,7 +35,7 @@ #define TECHWEB_TIER_5_POINTS 200 //! Amount of points gained per second by a single R&D server, see: [research][code/controllers/subsystem/research.dm] -#define TECHWEB_SINGLE_SERVER_INCOME 1 +#define TECHWEB_SINGLE_SERVER_INCOME 0.2 //! Swab cell line types #define CELL_LINE_TABLE_SLUDGE "cell_line_sludge_table" diff --git a/code/__defines/research/techweb_nodes.dm b/code/__defines/research/techweb_nodes.dm index 186d476878..c596ec298f 100644 --- a/code/__defines/research/techweb_nodes.dm +++ b/code/__defines/research/techweb_nodes.dm @@ -4,6 +4,8 @@ #define TECHWEB_NODE_ALIEN_SURGERY "alien_surgery" #define TECHWEB_NODE_ALIENTECH "alientech" #define TECHWEB_NODE_ANOMALY_RESEARCH "anomaly_research" +#define TECHWEB_NODE_ANOMALY_HARVESTING "anomaly_harvesting" +#define TECHWEB_NODE_APPLIED_ANOMALY_HARVESTING "applied_anomaly_harvesting" #define TECHWEB_NODE_ANOMALY_SHELLS "anomaly_shells" #define TECHWEB_NODE_TELEKINETIC_RESEARCH "telekinetic_research" #define TECHWEB_NODE_APPLIED_BLUESPACE "applied_bluespace" @@ -144,6 +146,7 @@ #define TECHWEB_NODE_TACKLE_ADVANCED "tackle_advanced" #define TECHWEB_NODE_TELECOMS "telecoms" #define TECHWEB_NODE_UNREGULATED_BLUESPACE "unregulated_bluespace" +#define TECHWEB_NODE_XENOARCHEOLOGY "xenoarcheology" #define TECHWEB_NODE_XENOBIOLOGY "xenobiology" #define TECHWEB_NODE_TELEPORTER "teleporter" #define TECHWEB_NODE_CONFETTI "confetti" diff --git a/code/_helpers/global_lists_vr.dm b/code/_helpers/global_lists_vr.dm index 9484f44fe6..967187ebe6 100644 --- a/code/_helpers/global_lists_vr.dm +++ b/code/_helpers/global_lists_vr.dm @@ -1280,7 +1280,9 @@ GLOBAL_LIST_INIT(area_or_turf_fail_types, typecacheof(list( /obj/item/disposable_teleporter/slime, \ /obj/item/slimepotion, \ /obj/item/slime_extract, \ - /obj/item/reagent_containers/food/snacks/monkeycube + /obj/item/reagent_containers/food/snacks/monkeycube, \ + /obj/item/anomaly_releaser, \ + /obj/item/research_sample #define CIRCUIT_GRIPPER \ /obj/item/cell/device, \ diff --git a/code/datums/outfits/jobs/science.dm b/code/datums/outfits/jobs/science.dm index 9552e84bdb..5311de6b03 100644 --- a/code/datums/outfits/jobs/science.dm +++ b/code/datums/outfits/jobs/science.dm @@ -50,3 +50,8 @@ backpack = /obj/item/storage/backpack satchel_one = /obj/item/storage/backpack/satchel/norm suit = /obj/item/clothing/suit/storage/toggle/labcoat/roboticist + +/datum/decl/hierarchy/outfit/job/science/scientist/anomalist + name = OUTFIT_JOB_NAME(JOB_ALT_ANOMALIST) + l_pocket = /obj/item/anomaly_scanner + r_pocket = /obj/item/anomaly_releaser/science diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm index 7271181aa1..7c1fbe459f 100644 --- a/code/game/jobs/job/science.dm +++ b/code/game/jobs/job/science.dm @@ -103,6 +103,7 @@ title = JOB_ALT_ANOMALIST title_blurb = "An " + JOB_ALT_ANOMALIST + " is a " + JOB_SCIENTIST + " whose expertise is analyzing alien artifacts. They are familar with the most common methods of testing artifact \ function. They work closely with " + JOB_ALT_XENOARCHAEOLOGIST + "s, or Miners, if either role is present." + title_outfit = /datum/decl/hierarchy/outfit/job/science/scientist/anomalist /datum/alt_title/phoron_research title = JOB_ALT_PHORON_RESEARCHER diff --git a/code/game/objects/effects/anomalies/_anomalies.dm b/code/game/objects/effects/anomalies/_anomalies.dm index 5afd62f428..9d882cb189 100644 --- a/code/game/objects/effects/anomalies/_anomalies.dm +++ b/code/game/objects/effects/anomalies/_anomalies.dm @@ -19,6 +19,10 @@ var/immortal = FALSE var/move_chance = ANOMALY_MOVECHANCE + // Anomaly harvesting stuff + var/datum/anomaly_stats/stats + var/danger_mult = 1 + /obj/effect/anomaly/Initialize(mapload, new_lifespan, drops_core = TRUE) . = ..() @@ -51,6 +55,7 @@ /obj/effect/anomaly/process(seconds_per_tick) anomalyEffect(seconds_per_tick) + anomalyPulse() if(death_time < world.time && !immortal) if(loc) detonate() @@ -60,12 +65,27 @@ STOP_PROCESSING(SSobj, src) QDEL_NULL(countdown) QDEL_NULL(anomaly_core) + if(stats) + QDEL_NULL(stats) return ..() /obj/effect/anomaly/proc/anomalyEffect(seconds_per_tick) - if(prob(move_chance)) + if(prob(move_chance) && !locate(/obj/effect/suspension_field) in get_turf(src)) move_anomaly() +// Used in anomaly harvesting - Normal anomalies shouldn't pulse +/obj/effect/anomaly/proc/anomalyPulse() + if(!stats) + return FALSE + if(world.time < stats.next_activation) + return FALSE + + stats.pulse_effect() + if(QDELETED(src)) + return FALSE + stats.next_activation = world.time + rand(stats.min_activation, stats.max_activation) + return TRUE + /obj/effect/anomaly/proc/move_anomaly() step(src, pick(GLOB.alldirs)) @@ -85,21 +105,56 @@ anomaly_core = null qdel(src) -/obj/effect/anomaly/proc/stabilize(anchor = FALSE, has_core = TRUE) +/obj/effect/anomaly/proc/stabilize(anchor = FALSE, has_core = TRUE, add_stats = FALSE) immortal = TRUE name = (has_core ? "stable " : "hollow ") + name if(!has_core) QDEL_NULL(anomaly_core) if(anchor) move_chance = 0 + if(!stats && add_stats) + stats = new /datum/anomaly_stats + stats.attached_anomaly = WEAKREF(src) + stats.calculate_points() + density = TRUE + return /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 + if(istype(I, /obj/item/anomaly_scanner)) + var/obj/item/anomaly_scanner/scanner = I + if(stats) + if(!do_after(user, 1 SECOND, src)) + return + scanner.buffered_anomaly = WEAKREF(src) + scanner.tgui_interact(user) + return TRUE return ..() +/obj/effect/anomaly/bullet_act(obj/item/projectile/proj) + if(stats && istype(stats.modifier, /datum/anomaly_modifiers/reflective) && prob(stats.severity/1.5)) + balloon_alert_visible("reflected!") + + var/new_x = proj.x = pick(0, 0, 0, -1, 1, -2, 2) + var/new_y = proj.y = pick(0, 0, 0, -1, 1, -2, 2) + + var/turf/curloc = get_step(proj, get_dir(proj, proj.starting)) + + proj.penetrating += 1 + + proj.redirect(new_x, new_y, curloc, null) + return FALSE + + if(istype(proj, /obj/item/projectile/energy/anomaly)) + var/obj/item/projectile/energy/anomaly/anom_proj = proj + if(stats) + stats.particle_hit(anom_proj.particle_type) + + return FALSE + /proc/generate_anomaly(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5, has_changed_lifespan = TRUE, drops_core = TRUE) var/turf/local_turf = pick(RANGE_TURFS(anomalyrange, anomalycenter)) if(!local_turf) diff --git a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm index 2a64422a5d..9c5562ca0f 100644 --- a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm +++ b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm @@ -5,6 +5,7 @@ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE layer = ABOVE_MOB_LAYER lifespan = ANOMALY_COUNTDOWN_TIMER * 2 + danger_mult = 1.1 /// Who are we moving towards? var/datum/weakref/pursuit_target @@ -21,6 +22,8 @@ /obj/effect/anomaly/bioscrambler/anomalyEffect(seconds_per_tick) . = ..() + if(stats) + return if(!COOLDOWN_FINISHED(src, pulse_cooldown)) return @@ -87,3 +90,15 @@ /obj/effect/anomaly/bioscrambler/detonate() COOLDOWN_RESET(src, pulse_cooldown) anomalyEffect() + +/obj/effect/anomaly/bioscrambler/anomalyPulse() + if(!..()) + return + + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + else + anomalyEffect() diff --git a/code/game/objects/effects/anomalies/anomalies_bluespace.dm b/code/game/objects/effects/anomalies/anomalies_bluespace.dm index a079eb15ce..b2379c52e9 100644 --- a/code/game/objects/effects/anomalies/anomalies_bluespace.dm +++ b/code/game/objects/effects/anomalies/anomalies_bluespace.dm @@ -7,6 +7,7 @@ var/teleport_range = 1 ///Distance we can teleport someone passively var/teleport_distance = 4 + danger_mult = 1.1 /obj/effect/anomaly/bluespace/Initialize(mapload, new_lifespan) . = ..() @@ -92,3 +93,43 @@ color = COLOR_BLUE_LIGHT duration = 1 SECONDS amount_to_scale = 5 + +/obj/effect/anomaly/bluespace/anomalyPulse() + if(!..()) + return + + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + if(16 to 33) + pulse_teleport(4, 1) + if(34 to 65) + pulse_teleport(5, 2) + else + pulse_teleport(6, 3) + +/obj/effect/anomaly/bluespace/proc/pulse_teleport(range, count) + 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 + + var/chosen = pick(possible) + + var/list/things = list() + for(var/atom/movable/thing in orange(range, get_turf(src))) + if(istype(thing, /obj/item/radio/beacon) || iseffect(thing) || isEye(thing)) + continue + if(thing.anchored) + continue + things += thing + + for(var/i in 1 to count) + do_teleport(pick(things), get_turf(chosen)) diff --git a/code/game/objects/effects/anomalies/anomalies_dimensional.dm b/code/game/objects/effects/anomalies/anomalies_dimensional.dm index be5dcc94f7..9d50def69c 100644 --- a/code/game/objects/effects/anomalies/anomalies_dimensional.dm +++ b/code/game/objects/effects/anomalies/anomalies_dimensional.dm @@ -4,6 +4,7 @@ anomaly_core = /obj/item/assembly/signaler/anomaly/dimensional lifespan = ANOMALY_COUNTDOWN_TIMER * 20 move_chance = 0 + danger_mult = 0.8 /// 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 @@ -40,6 +41,8 @@ /obj/effect/anomaly/dimensional/proc/transmute_area() if(!theme) prepare_area() + if(stats) + return if(!target_turfs.len) if(teleports_left <= 0 && !immortal) detonate() @@ -88,3 +91,26 @@ icon = 'icons/effects/effects.dmi' icon_state = "shieldsparkles" duration = 3 + +/obj/effect/anomaly/dimensional/anomalyPulse() + if(!..()) + return + + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + if(16 to 33) + pulse_change(2) + if(34 to 65) + pulse_change(4) + else + pulse_change(8) + +/obj/effect/anomaly/dimensional/proc/pulse_change(count) + var/turf/turf + + for(var/i in 1 to count) + turf = pick(target_turfs) + theme.apply_theme(turf, show_effect = TRUE) diff --git a/code/game/objects/effects/anomalies/anomalies_dust.dm b/code/game/objects/effects/anomalies/anomalies_dust.dm index 9997e15ba3..5e89063582 100644 --- a/code/game/objects/effects/anomalies/anomalies_dust.dm +++ b/code/game/objects/effects/anomalies/anomalies_dust.dm @@ -6,6 +6,7 @@ pass_flags = PASSTABLE | PASSGRILLE layer = ABOVE_MOB_LAYER lifespan = ANOMALY_COUNTDOWN_TIMER * 1.5 + danger_mult = 0.75 move_chance = 80 @@ -71,3 +72,15 @@ /obj/effect/anomaly/dust/detonate() COOLDOWN_RESET(src, pulse_cooldown) anomalyEffect() + +/obj/effect/anomaly/dust/anomalyPulse() + if(!..()) + return + + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + else + anomalyEffect() diff --git a/code/game/objects/effects/anomalies/anomalies_flux.dm b/code/game/objects/effects/anomalies/anomalies_flux.dm index 97aa3dc13d..fc9a63c417 100644 --- a/code/game/objects/effects/anomalies/anomalies_flux.dm +++ b/code/game/objects/effects/anomalies/anomalies_flux.dm @@ -61,3 +61,24 @@ /obj/effect/anomaly/flux/minor/Initialize(mapload, new_lifespan, emp_zap = FLUX_NO_EMP) return ..() + +/obj/effect/anomaly/flux/anomalyPulse() + if(!..()) + return + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + if(16 to 33) + tesla_zap(src, 2, 1000, FALSE, FALSE) + if(34 to 65) + tesla_zap(src, 3, 1000, FALSE, FALSE) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(tesla_zap), src, 3, 1500, FALSE, FALSE), 3 SECONDS) + else + tesla_zap(src, 4, 1000, FALSE, TRUE) + addtimer(CALLBACK(src, PROC_REF(highSevPulse)), 3 SECONDS) + +/obj/effect/anomaly/flux/proc/highSevPulse() + tesla_zap(src, 4, 1250, FALSE, FALSE) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(tesla_zap), src, 4, 1500, FALSE, FALSE), 3 SECONDS) diff --git a/code/game/objects/effects/anomalies/anomalies_gravity.dm b/code/game/objects/effects/anomalies/anomalies_gravity.dm index bb80156112..519c03f4b4 100644 --- a/code/game/objects/effects/anomalies/anomalies_gravity.dm +++ b/code/game/objects/effects/anomalies/anomalies_gravity.dm @@ -16,6 +16,8 @@ /obj/effect/anomaly/grav/anomalyEffect(seconds_per_tick) ..() + if(stats) + return boing = TRUE for(var/obj/O in orange(4, src)) if(!O.anchored) @@ -93,3 +95,14 @@ */ /obj/effect/temp_visual/circle_wave/gravity color = COLOR_NAVY + +/obj/effect/anomaly/grav/anomalyPulse() + if(!..()) + return + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + else + anomalyEffect() diff --git a/code/game/objects/effects/anomalies/anomalies_hallucination.dm b/code/game/objects/effects/anomalies/anomalies_hallucination.dm index 7390896a38..9cfe28a14e 100644 --- a/code/game/objects/effects/anomalies/anomalies_hallucination.dm +++ b/code/game/objects/effects/anomalies/anomalies_hallucination.dm @@ -2,6 +2,7 @@ name = "hallucination anomaly" icon_state = "hallucination" anomaly_core = /obj/item/assembly/signaler/anomaly/hallucination + danger_mult = 0.9 /// 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 @@ -23,6 +24,8 @@ /obj/effect/anomaly/hallucination/anomalyEffect(seconds_per_tick) . = ..() + if(stats) + return ticks += seconds_per_tick if(ticks < release_delay) return @@ -109,3 +112,14 @@ ///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 + +/obj/effect/anomaly/hallucination/anomalyPulse() + if(!..()) + return + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + else + anomalyEffect() diff --git a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm index bac080489c..576fd154c0 100644 --- a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm +++ b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm @@ -1,6 +1,7 @@ /obj/effect/anomaly/pyro name = "pyroclastic anomaly" icon_state = "pyroclastic" + danger_mult = 1.3 var/ticks = 0 /// How many seconds between each gas release var/releasedelay = 15 SECONDS @@ -12,23 +13,40 @@ /obj/effect/anomaly/pyro/anomalyEffect(seconds_per_tick) ..() + if(stats) + return 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) + burst() return TRUE -/obj/effect/anomaly/pyro/detonate() +/obj/effect/anomaly/pyro/proc/burst(moles = 10, temperature = 700, volume = 400) var/turf/simulated/floor/tile = get_turf(src) if(istype(tile)) - tile.assume_gas(GAS_PHORON, 10, T20C) - tile.hotspot_expose(700, 400) + tile.assume_gas(GAS_PHORON, moles, T20C) + tile.hotspot_expose(temperature, volume) + +/obj/effect/anomaly/pyro/detonate() + burst() 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) + var/mob/living/simple_mob/slime/xenobio/pyro = new new_colour(get_turf(src)) pyro.enrage() + +/obj/effect/anomaly/pyro/anomalyPulse() + if(!..()) + return + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + if(16 to 33) + burst() + if(34 to 65) + burst(20, 900, 600) + else + burst(30, 110, 800) diff --git a/code/game/objects/effects/anomalies/anomalies_weather.dm b/code/game/objects/effects/anomalies/anomalies_weather.dm index baa4366de2..ca21a910c3 100644 --- a/code/game/objects/effects/anomalies/anomalies_weather.dm +++ b/code/game/objects/effects/anomalies/anomalies_weather.dm @@ -44,6 +44,12 @@ addtimer(CALLBACK(src, PROC_REF(start_weather)), telegraph, TIMER_DELETE_ME) +/obj/effect/anomaly/weather/proc/add_turfs(list/turf/to_add) + for(var/turf/turf in to_add) + if(isspace(turf)) + continue + affected_turfs.Add(turf) + /obj/effect/anomaly/weather/proc/find_adjacent_impacted_area(check_dir) var/limit = 10 var/turf/next_turf = get_step(src, check_dir) @@ -73,6 +79,9 @@ turf = GetBelow(turf) selected_weather.affect_turf(turf) + if(stats) + return + for(var/mob/mob as anything in GLOB.player_list) if(get_area(mob) in affected_areas) selected_weather.hear_sounds(mob, TRUE) @@ -104,13 +113,13 @@ if(selected_weather.sounds) selected_weather.loop_sounds.stop() + for(var/turf/turf in affected_turfs) + selected_weather.remove_from_turf(turf) + for(var/area in affected_areas) for(var/mob/mob in area) selected_weather.hear_sounds(mob, FALSE) - for(var/turf/turf in affected_turfs) - selected_weather.remove_from_turf(turf) - /obj/effect/anomaly/weather/Destroy() clear_weather() . = ..() @@ -118,6 +127,47 @@ /obj/effect/anomaly/weather/proc/update_reagent(reagent) selected_weather.update_reagent(reagent) +/obj/effect/anomaly/weather/anomalyPulse() + if(!..()) + return + switch(stats.severity) + if(0 to 15) + var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + affected_areas.Cut() + affected_turfs.Cut() + if(16 to 33) + clear_weather() + affected_turfs.Cut() + if(!istype(selected_weather, /datum/anomalous_weather/rain)) + selected_weather = new /datum/anomalous_weather/rain + update_reagent(REAGENT_ID_WATER) + add_turfs(circleviewturfs(src, 3)) + start_weather() + if(34 to 65) + clear_weather() + affected_turfs.Cut() + if(!istype(selected_weather, /datum/anomalous_weather/rain)) + selected_weather = new /datum/anomalous_weather/rain + update_reagent(pick(REAGENT_ID_WATER, REAGENT_ID_ICE, REAGENT_ID_ORANGEJUICE)) + add_turfs(circlerangeturfs(src, 4)) + start_weather() + else + clear_weather() + affected_turfs.Cut() + if(!istype(selected_weather, /datum/anomalous_weather/rain/storm)) + selected_weather = new /datum/anomalous_weather/rain/storm + + var/reagent_id = pick(SSchemistry.chemical_reagents) + if(reagent_id in GLOB.obtainable_chemical_blacklist) + update_reagent(REAGENT_ID_WATER) // You get WATER. + else + update_reagent(reagent_id) + + add_turfs(circlerangeturfs(src, 5)) + start_weather() + /obj/effect/anomaly/weather/rain selected_weather = /datum/anomalous_weather/rain diff --git a/code/game/objects/effects/anomalies/anomalies_weather_themes.dm b/code/game/objects/effects/anomalies/anomalies_weather_themes.dm index 1afbb5d225..8290f6f11f 100644 --- a/code/game/objects/effects/anomalies/anomalies_weather_themes.dm +++ b/code/game/objects/effects/anomalies/anomalies_weather_themes.dm @@ -57,7 +57,7 @@ T.vis_contents -= visuals /datum/anomalous_weather/proc/affect_turf(turf/to_affect) - if(iswall(to_affect)) + if(iswall(to_affect) || isopenturf(to_affect)) return for(var/atom/thing in to_affect) @@ -130,7 +130,7 @@ drench_message = "Rain falls on you, drenching you in water." /datum/anomalous_weather/rain/do_special(turf/simulated/T) - if(prob(2)) + if(prob(2) && !isopenturf(T)) T.wet_floor(1) /datum/anomalous_weather/rain/blood @@ -153,9 +153,9 @@ drench_message = "Rain falls on you, completely soaking you." /datum/anomalous_weather/rain/storm/do_special(turf/simulated/T) - if(prob(3)) + if(prob(3) && !isopenturf(T)) T.wet_floor(1) - if(prob(0.025)) + if(prob(0.025) && !isopenturf(T)) lightning_strike(T) /datum/anomalous_weather/rain/acid diff --git a/code/game/objects/effects/anomalies/anomaly_stats.dm b/code/game/objects/effects/anomalies/anomaly_stats.dm new file mode 100644 index 0000000000..0174b331ed --- /dev/null +++ b/code/game/objects/effects/anomalies/anomaly_stats.dm @@ -0,0 +1,191 @@ +/datum/anomaly_stats + var/severity = 1 + var/stability + var/max_health + + var/danger_type + var/unstable_type + var/containment_type + var/transformation_type + + var/datum/anomaly_modifiers/modifier + + var/datum/weakref/attached_anomaly + var/datum/weakref/attached_harvester + + var/next_activation + // Total of points we'll get once the anomaly does a pulse + var/points + var/curr_health + var/flags + + var/min_activation = 45 SECONDS + var/max_activation = 90 SECONDS + +/datum/anomaly_stats/New() + randomize_particle_types() + severity = rand(5, 15) + max_health = rand(50, 150) + curr_health = max_health + stability = ANOMALY_STABLE + +/datum/anomaly_stats/Destroy(force) + QDEL_NULL(modifier) + return ..() + +/datum/anomaly_stats/proc/randomize_particle_types() + var/list/particles = list(ANOMALY_PARTICLE_SIGMA, ANOMALY_PARTICLE_DELTA, ANOMALY_PARTICLE_ZETA, ANOMALY_PARTICLE_EPSILON) + particles = shuffle(particles) + danger_type = particles[1] + unstable_type = particles[2] + containment_type = particles[3] + transformation_type = particles[4] + +/datum/anomaly_stats/proc/calculate_points() + var/total = 10 + + var/obj/effect/anomaly/anomaly = attached_anomaly.resolve() + + if(!anomaly) + return + + total += severity/5 + total *= curr_health/max_health + total *= anomaly.danger_mult + + if(modifier) + total *= modifier.get_value() + + points = total + + return points + +/datum/anomaly_stats/proc/particle_hit(particle) + // I don't really like this, but switch() expects a constant expression + if(particle == danger_type) + update_severity(1, 5) + return + else if(particle == unstable_type) + update_state(TRUE) + return + else if(particle == containment_type) + update_state(FALSE) + update_health(-1, -10) + return + else if(particle == transformation_type) + update_modifiers() + return + return + +/datum/anomaly_stats/proc/update_severity(lower, upper) + var/obj/effect/anomaly/anom = attached_anomaly.resolve() + if(!istype(anom)) + attached_anomaly = null + return + var/sev_change = rand(lower, upper) + severity += sev_change + + if(severity >= 100) + kill_anomaly(TRUE) + return + + var/scale_factor = 1.0+(severity/100) + var/matrix/M = matrix() + M.Scale(scale_factor, scale_factor) + animate(anom, transform = M, time = 1 SECOND) + + // These don't have the wibbly filter + if(!istype(anom, /obj/effect/anomaly/bioscrambler) && !istype(anom, /obj/effect/anomaly/dimensional)) + apply_wibbly_filters(anom) + + calculate_points() + return + +/datum/anomaly_stats/proc/update_health(lower, upper) + var/health_change = rand(lower, upper) + curr_health += health_change + + if(curr_health <= 0) + kill_anomaly(FALSE) + return + + calculate_points() + return + +/datum/anomaly_stats/proc/kill_anomaly(critical) + var/obj/effect/anomaly/anom = attached_anomaly.resolve() + if(!istype(anom)) + attached_anomaly = null + return + if(critical) + anom.detonate() + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + QDEL_NULL(anom) + return + +/datum/anomaly_stats/proc/update_state(unstable) + var/obj/effect/anomaly/anom = attached_anomaly.resolve() + + if(!istype(anom)) + return + + if(locate(/obj/effect/suspension_field) in get_turf(anom)) + return + + switch(stability) + if(ANOMALY_STABLE) + if(unstable && prob(15)) + stability = ANOMALY_GROWING + else if(prob(15)) + stability = ANOMALY_DECAYING + return + if(ANOMALY_GROWING) + if(!unstable && prob(15)) + stability = ANOMALY_STABLE + return + if(ANOMALY_DECAYING) + if(unstable && prob(15)) + stability = ANOMALY_STABLE + else + return + return + +/datum/anomaly_stats/proc/update_modifiers() + var/picked_mod = pick(subtypesof(/datum/anomaly_modifiers)) + + // Tough luck. No mod for you. + if(istype(modifier, picked_mod)) + return + + if(modifier) + modifier.on_remove(attached_anomaly) + modifier = null + + modifier = new picked_mod + modifier.on_add(attached_anomaly) + calculate_points() + return + +/datum/anomaly_stats/proc/get_activation_countdown() + return (next_activation - world.time)/10 + +/datum/anomaly_stats/proc/pulse_effect() + if(stability == ANOMALY_DECAYING) + update_health(-1, -10) + else if(stability == ANOMALY_GROWING) + update_severity(1, 10) + + if(prob(5)) + if(stability == ANOMALY_STABLE) + stability = pick(ANOMALY_DECAYING, ANOMALY_GROWING) + else + stability = ANOMALY_STABLE + + if(attached_harvester) + var/obj/machinery/anomaly_harvester/harvester = attached_harvester.resolve() + if(!istype(harvester)) + return + + harvester.add_points(points) diff --git a/code/game/objects/effects/anomalies/anomaly_stats_mods.dm b/code/game/objects/effects/anomalies/anomaly_stats_mods.dm new file mode 100644 index 0000000000..c36a57c214 --- /dev/null +++ b/code/game/objects/effects/anomalies/anomaly_stats_mods.dm @@ -0,0 +1,87 @@ +/datum/anomaly_modifiers + var/name + var/description + var/value + var/obj/effect/anomaly/attached_anomaly = null + +/datum/anomaly_modifiers/proc/get_description() + return description + +/datum/anomaly_modifiers/proc/get_value() + return value + +/datum/anomaly_modifiers/proc/on_add(datum/weakref/anomaly) + attached_anomaly = anomaly.resolve() + if(!istype(attached_anomaly)) + return FALSE + return TRUE + +/datum/anomaly_modifiers/proc/on_remove(datum/weakref/anomaly) + attached_anomaly = anomaly.resolve() + if(!istype(attached_anomaly)) + return FALSE + return TRUE + +/datum/anomaly_modifiers/reflective + name = "Reflective" + description = "A protective coating was detected." + value = 1.2 + +/datum/anomaly_modifiers/invisible + name = "Invisible" + description = "Light wave distortion was detected." + value = 1.5 + +/datum/anomaly_modifiers/invisible/on_add(datum/weakref/anomaly) + if(!..()) + return + addtimer(CALLBACK(attached_anomaly, TYPE_PROC_REF(/atom/movable, cloak)), 2 SECONDS) + +/datum/anomaly_modifiers/invisible/on_remove(datum/weakref/anomaly) + if(!..()) + return + addtimer(CALLBACK(attached_anomaly, TYPE_PROC_REF(/atom/movable, uncloak)), 2 SECONDS) + +/* +/datum/anomaly_modifiers/hidden + name = "Hidden" + description = "Interference detected. Some data cannot be read." + value = 0.15 +*/ + +/datum/anomaly_modifiers/move + name = "Move" + description = "Anomalous anchoring could not be detected." + value = 1.4 + +/datum/anomaly_modifiers/move/on_add(datum/weakref/anomaly) + if(!..()) + return + attached_anomaly.move_chance = ANOMALY_MOVECHANCE + +/datum/anomaly_modifiers/move/on_remove(datum/weakref/anomaly) + if(!..()) + return + attached_anomaly.move_chance = 0 + +/datum/anomaly_modifiers/fast + name = "Faster Pulses" + description = "Anomalous pulses are more common." + value = 0.9 + +/datum/anomaly_modifiers/fast/on_add(datum/weakref/anomaly) + if(!..()) + return + + var/datum/anomaly_stats/stats = attached_anomaly.stats + stats.min_activation = 25 SECONDS + stats.max_activation = 45 SECONDS + +/datum/anomaly_modifiers/fast/on_remove(datum/weakref/anomaly) + if(!..()) + return + + var/datum/anomaly_stats/stats = attached_anomaly.stats + + stats.min_activation = initial(stats.min_activation) + stats.max_activation = initial(stats.max_activation) diff --git a/code/game/objects/items/devices/anomaly.dm b/code/game/objects/items/devices/anomaly.dm deleted file mode 100644 index 0fe6e77191..0000000000 --- a/code/game/objects/items/devices/anomaly.dm +++ /dev/null @@ -1,43 +0,0 @@ -/obj/item/anomaly_neutralizer - name = "anomaly neutralizer" - desc = "A one-use device capable of instantly neutralizing anomalous or otherworldly entities." - icon = 'icons/obj/devices/tool.dmi' - icon_state = "neutralyzer" - w_class = ITEMSIZE_SMALL - slot_flags = SLOT_BELT - item_flags = NOBLUDGEON - -/obj/item/anomaly_neutralizer/Initialize(mapload) - . = ..() - - AddComponent(/datum/component/effect_remover, \ - success_feedback = "You neutralize %THEEFFECT with %THEWEAPON, frying its circuitry in the process.", \ - on_clear_callback = CALLBACK(src, PROC_REF(on_anomaly_neutralized)), \ - effects_we_clear = list(/obj/effect/anomaly)) - -/obj/item/anomaly_neutralizer/proc/on_anomaly_neutralized(obj/effect/anomaly/target, mob/living/user) - target.anomalyNeutralize() - on_use(target, user) - -/obj/item/anomaly_neutralizer/proc/on_use(obj/effect/target, mob/living/user) - var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread - sparks.set_up(3, 1, src) - sparks.start() - qdel(src) - -/obj/item/anomaly_releaser - icon = 'icons/obj/devices/syndie_gadget.dmi' - icon_state = "anomaly_releaser" - name = "anomaly releaser" - desc = "Single-use injector that releases and stabilizes anomalies by injecting an unknown substance." - throwforce = 0 - w_class = ITEMSIZE_SMALL - throw_speed = 3 - throw_range = 5 - - ///icon state after being used up - var/used_icon_state = "anomaly_releaser_used" - ///are we used? if used we can't be used again - var/used = FALSE - ///Can we be used infinitely? - var/infinite = FALSE diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index d88b6ef31e..1f8406147e 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -100,3 +100,10 @@ icon_state = "multitool" toolspeed = 0.1 origin_tech = list(TECH_MAGNET = 5, TECH_ENGINEERING = 5) + +// Alien multitool only has those icon states +/obj/item/multitool/alien/update_icon() + if(accepting_refs) + icon_state = "multitool_ref_scan" + return + icon_state = "multitool" diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index a75eba2a57..c47c9ec042 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -546,6 +546,29 @@ to_chat(R, span_notice("Letter compartment upgraded!")) return TRUE +// Anomaly Gun +/obj/item/borg/upgrade/restricted/anomalygun + name = "robot mounted particle gun" + desc = "A anomaly particle gun adapted for installation in cyborgs, allows them to shoot different particles upon anomalies." + icon_state = "cyborg_upgrade2" + item_state = "cyborg_upgrade" + module_flags = BORG_MODULE_SCIENCE + require_module = TRUE + +/obj/item/borg/upgrade/restricted/anomalygun/action(mob/user, mob/living/silicon/robot/R) + if(..()) return FALSE + + if(!R.supports_upgrade(type)) + generic_error(user, R, type) + return FALSE + + if(R.has_restricted_upgrade(type)) + generic_error(user, R, type) + return FALSE + + R.module.modules += new/obj/item/gun/energy/anomaly/mounted(R.module) + return TRUE + /* ############################################### # Unsorted section. All cargo modules go here.# ###############################################*/ diff --git a/code/game/objects/items/weapons/circuitboards/machinery/anomaly_harvester.dm b/code/game/objects/items/weapons/circuitboards/machinery/anomaly_harvester.dm new file mode 100644 index 0000000000..a7a32ea1dd --- /dev/null +++ b/code/game/objects/items/weapons/circuitboards/machinery/anomaly_harvester.dm @@ -0,0 +1,17 @@ +#ifndef T_BOARD +#error T_BOARD macro is not defined but we need it! +#endif + +/obj/item/circuitboard/anomaly_harvester + name = T_BOARD("anomaly harvester") + build_path = /obj/machinery/anomaly_harvester + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_DATA = 1) + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/material/phoron = 5, + /obj/item/stack/material/diamond = 1 + ) diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index 9383b94286..39d068656d 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -86,7 +86,9 @@ /obj/item/stack/nanopaste, /obj/item/geiger, /obj/item/reagent_scanner, - /obj/item/lightpainter + /obj/item/lightpainter, + /obj/item/anomaly_releaser, + /obj/item/anomaly_scanner ) /obj/item/storage/belt/utility/full diff --git a/code/modules/anomalies/anomaly.dm b/code/modules/anomalies/anomaly.dm new file mode 100644 index 0000000000..fa70de057b --- /dev/null +++ b/code/modules/anomalies/anomaly.dm @@ -0,0 +1,162 @@ +/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 = "advanced 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 + //If the created anomaly leaves a core behind + var/has_core = TRUE + //If this will anchor the anomaly in place + var/will_anchor = TRUE + // If it will apply stats to it + var/gives_stats = TRUE + +/obj/item/anomaly_releaser/science + icon = 'icons/obj/devices/tool.dmi' + icon_state = "sci_releaser" + name = "scientific anomaly releaser" + used_icon_state = "sci_releaser_used" + has_core = FALSE + +// The one for antags and evil-doers +/obj/item/anomaly_releaser/antag + has_core = TRUE + will_anchor = TRUE + gives_stats = FALSE // Evil and fucked up... + desc = "Single-use injector that releases and stabilizes anomalies by injecting an unknown substance. This one seems odd." + +/obj/item/anomaly_scanner + name = "anomaly scanner" + desc = "A hand-held anomaly scanner, able to distinguish the particles that might affect a stable anomaly." + icon = 'icons/obj/device.dmi' + icon_state = "anom_scanner" + slot_flags = SLOT_BELT + w_class = ITEMSIZE_SMALL + throw_speed = 5 + throw_range = 10 + matter = list(MAT_STEEL = 200) + + pickup_sound = 'sound/items/pickup/device.ogg' + drop_sound = 'sound/items/drop/device.ogg' + + var/datum/weakref/buffered_anomaly = null + +/obj/item/anomaly_scanner/attack_self(mob/living/user) + . = ..(user) + if(.) + return TRUE + tgui_interact(user) + +/obj/item/anomaly_scanner/tgui_static_data(mob/user) + . = ..() + if(isrobot(loc)) + var/mob/living/silicon/robot/robot_owner = loc + .["theme"] = robot_owner.get_ui_theme() + +/obj/item/anomaly_scanner/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AnomalyScanner", name) + ui.open() + +/obj/item/anomaly_scanner/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/data = list() + var/obj/effect/anomaly/anom = buffered_anomaly.resolve() + + if(!istype(anom)) + return data + + var/datum/anomaly_stats/stats = anom.stats + + data["anomaly_name"] = anom.name + data["severity"] = stats.severity + data["stability"] = stats.stability + data["point_output"] = stats.points + data["danger_type"] = stats.danger_type + data["unstable_type"] = stats.unstable_type + data["containment_type"] = stats.containment_type + data["transformation_type"] = stats.transformation_type + if(stats.modifier) + data["modifier"] = stats.modifier.get_description() + data["countdown"] = stats.get_activation_countdown() + + return data + +/obj/item/gun/energy/anomaly + name = "anomalous particle gun" + desc = "A handheld particle emitter, used to safely release a specific particle frequency." + icon_state = "taserblue" + fire_delay = 4 + projectile_type = /obj/item/projectile/energy/anomaly + fire_sound = 'sound/weapons/taser2.ogg' + recoil_mode = 0 + accuracy = 30 + + var/particle = ANOMALY_PARTICLE_SIGMA + +/obj/item/gun/energy/anomaly/mounted + name = "mounted particle gun" + self_recharge = 1 + use_external_power = 1 + +/obj/item/gun/energy/anomaly/attack_self(mob/user) + var/chosen_particle = tgui_input_list(user, "Select particle type", "Particle Selection", ANOMALY_PARTICLE_ALL) + if(!chosen_particle) + return + + particle = chosen_particle + balloon_alert_visible("changed to [chosen_particle]") + ..() + +/obj/item/gun/energy/anomaly/consume_next_projectile() + var/obj/item/cell/battery = power_supply + + if(use_external_power) + battery = get_external_power_supply() + + if(!battery || !battery.checked_use(charge_cost)) + return null + var/mob/living/M = loc + if(istype(M)) + M.hud_used.update_ammo_hud(M, src) + var/obj/item/projectile/energy/anomaly/projectile = new + if(particle) + projectile.particle_type = particle + return projectile diff --git a/code/modules/anomalies/anomaly_harvester.dm b/code/modules/anomalies/anomaly_harvester.dm new file mode 100644 index 0000000000..ac04897dc8 --- /dev/null +++ b/code/modules/anomalies/anomaly_harvester.dm @@ -0,0 +1,200 @@ +/obj/machinery/anomaly_harvester + name = "anomaly harvester" + desc = "A strange device that condenses anomalous energy into tangible material." + icon = 'icons/obj/machines/anomaly_harvester.dmi' + icon_state = "harvester" + density = TRUE + circuit = /obj/item/circuitboard/anomaly_harvester + active_power_usage = 40000 + idle_power_usage = 1000 + + var/points = 0 + var/points_to_create = 100 + var/efficiency = 1 + + var/datum/weakref/harvested + var/list/obj/item/research_sample/samples + +/obj/machinery/anomaly_harvester/Initialize(mapload) + . = ..() + default_apply_parts() + +/obj/machinery/anomaly_harvester/Destroy() + harvested = null + return ..() + +/obj/machinery/anomaly_harvester/RefreshParts() + var/efficient = 0 + var/rating = 0 + for(var/obj/item/stock_parts/manipulator/manipulator in component_parts) + efficient += manipulator.rating - 2 + for(var/obj/item/stock_parts/micro_laser/laser in component_parts) + rating += laser.rating - 2 + + efficiency = max(1, (efficient/10+1)) + points_to_create = min(100, (100 - (rating * 5))) + +/obj/machinery/anomaly_harvester/process() + ..() + if(stat & (NOPOWER|BROKEN) || !anchored) + update_use_power(USE_POWER_OFF) + else + update_use_power(USE_POWER_IDLE) + harvest_anomaly() + if(points && points >= points_to_create) + points -= points_to_create + generate_sample() + update_icon() + +/obj/machinery/anomaly_harvester/proc/add_points(add_points) + add_points *= efficiency + points += add_points + return + +/obj/machinery/anomaly_harvester/proc/harvest_anomaly() + if(!harvested) + return + + var/obj/effect/anomaly/anom = harvested.resolve() + if(!istype(anom)) + return + + var/datum/anomaly_stats/stats = anom.stats + + if(stats.stability == ANOMALY_DECAYING) + playsound(src, 'sound/machines/2beephigh.ogg', 75) + else if (stats.stability == ANOMALY_GROWING) + playsound(src, 'sound/machines/buzzbeep.ogg', 75) + +/obj/machinery/anomaly_harvester/attackby(obj/item/W, mob/user, attack_modifier, click_parameters) + add_fingerprint(user) + + if(default_deconstruction_screwdriver(user, W)) + return + if(default_deconstruction_crowbar(user, W)) + return + if(default_part_replacement(user, W)) + return + if(default_unfasten_wrench(user, W, 2 SECONDS)) + return + if(istype(W, /obj/item/anomaly_scanner)) + if(!anchored) + to_chat(user, span_danger("The [src] is not anchored!")) + return + var/obj/item/anomaly_scanner/scanner = W + if(scanner.buffered_anomaly && do_after(user, 2 SECONDS, src)) + attach_anomaly(scanner.buffered_anomaly) + return + + return ..() + +/obj/machinery/anomaly_harvester/default_unfasten_wrench(mob/user, obj/item/W, time) + . = ..() + harvested = null + +/obj/machinery/anomaly_harvester/proc/attach_anomaly(datum/weakref/anomaly) + var/obj/effect/anomaly/anom = anomaly.resolve() + if(!istype(anom)) + return + + var/datum/anomaly_stats/stats = anom.stats + if(stats.attached_harvester) + var/obj/machinery/anomaly_harvester/harvester = stats.attached_harvester.resolve() + harvester.harvested = null + harvester.update_icon() + stats.attached_harvester = null + harvested = anomaly + stats.attached_harvester = WEAKREF(src) + playsound(src, 'sound/machines/boobeebeep.ogg', 75, TRUE) + return TRUE + +/obj/machinery/anomaly_harvester/proc/generate_sample() + update_use_power(USE_POWER_ACTIVE) + playsound(src, 'sound/machines/ping.ogg', 50, TRUE) + switch(rand(1, 100)) + if(1 to 60) + new /obj/item/research_sample/common(src) + if(61 to 85) + new /obj/item/research_sample/uncommon(src) + if(86 to 95) + new /obj/item/research_sample/rare(src) + if(96 to 100) + new /obj/item/research_sample/bluespace(src) + else + new /obj/item/research_sample/common(src) + +/obj/machinery/anomaly_harvester/update_icon() + cut_overlays() + if(stat & (NOPOWER|BROKEN) || !anchored) + add_overlay("harvester_off") + else + add_overlay("harvester_on") + + if(harvested) + var/obj/effect/anomaly/anom = harvested.resolve() + if(!istype(anom)) + return + + var/datum/anomaly_stats/stats = anom.stats + + switch(stats.stability) + if(ANOMALY_STABLE) + add_overlay("harvester_stable") + if(ANOMALY_DECAYING) + add_overlay("harvester_decay") + else + add_overlay("harvester_grow") + +/obj/machinery/anomaly_harvester/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/anomaly_harvester/attack_hand(mob/user) + if(..()) + return + tgui_interact(user) + +/obj/machinery/anomaly_harvester/tgui_state(mob/user) + return GLOB.tgui_default_state + +/obj/machinery/anomaly_harvester/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AnomalyHarvester", name) + ui.open() + +/obj/machinery/anomaly_harvester/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/sample_data = list() + for(var/obj/item/research_sample/sample in src) + UNTYPED_LIST_ADD(sample_data, list( + "name" = sample.name, + "icon" = sample.icon, + "icon_state" = sample.icon_state, + "ref" = REF(sample) + )) + + var/obj/effect/anomaly/anom = harvested?.resolve() + var/list/data = list( + "name" = anom, + "points" = points, + "pointsToGenerate" = points_to_create, + "samples" = sample_data + ) + + return data + +/obj/machinery/anomaly_harvester/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + . = ..() + if(.) + return + + switch(action) + if("release_sample") + var/obj/item/research_sample/sample = locate(params["ref"]) in src + if(!istype(sample) || (sample.loc != src)) + return FALSE + sample.forceMove(get_turf(src)) + return TRUE + if("release_all") + for(var/obj/item/research_sample/sample in src) + sample.forceMove(get_turf(src)) + return TRUE diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station.dm b/code/modules/mob/living/silicon/robot/robot_modules/station.dm index db07152997..c8145770ec 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm @@ -680,7 +680,7 @@ /obj/item/robot_module/robot/research name = "research module" channels = list(CHANNEL_SCIENCE = 1) - supported_upgrades = list(/obj/item/borg/upgrade/restricted/advrped) + supported_upgrades = list(/obj/item/borg/upgrade/restricted/advrped, /obj/item/borg/upgrade/restricted/anomalygun) pto_type = PTO_SCIENCE /obj/item/robot_module/robot/research/create_equipment(var/mob/living/silicon/robot/robot) @@ -706,6 +706,7 @@ // Anomaly handling src.modules += new /obj/item/analyzer(src) src.modules += new /obj/item/assembly/signaler(src) + src.modules += new /obj/item/anomaly_scanner(src) src.emag += new /obj/item/hand_tele(src) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index e0634cb52f..eb807b54d7 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -23,6 +23,10 @@ var/state = 0 var/locked = 0 + // Anomaly harvesting stuff + var/anomalous = FALSE + var/particle = ANOMALY_PARTICLE_SIGMA + var/burst_delay = 2 var/initial_fire_delay = 100 @@ -59,13 +63,13 @@ if(!src.locked) if(src.active==1) src.active = 0 - to_chat(user, "You turn off [src].") - message_admins("Emitter turned off by [key_name(user, user.client)](?) in ([x],[y],[z] - JMP)") + balloon_alert_visible("turned off") + message_admins("Emitter turned off by [key_name(user, user.client)](?) in ([x],[y],[z] - JMP)",0,1) log_game("EMITTER([x],[y],[z]) OFF by [key_name(user)]") investigate_log("turned " + span_red("off") + " by [user.key]","singulo") else src.active = 1 - to_chat(user, "You turn on [src].") + balloon_alert_visible("turned on") src.shot_number = 0 src.fire_delay = get_initial_fire_delay() message_admins("Emitter turned on by [key_name(user, user.client)](?) in ([x],[y],[z] - JMP)") @@ -223,6 +227,18 @@ else to_chat(user, span_warning("Access denied.")) return + if(istype(W, /obj/item/anomaly_scanner)) + anomalous = !anomalous + burst_delay = anomalous ? 3 : 8 + to_chat(user, span_notice("The beam is now set to [anomalous ? "anomalous." : "normal."]")) + return + if(W.has_tool_quality(TOOL_MULTITOOL) && anomalous) + var/chosen_particle = tgui_input_list(user, "Select particle type", "Particle Selection", ANOMALY_PARTICLE_ALL) + if(!chosen_particle) + return + particle = chosen_particle + balloon_alert_visible("changed to [chosen_particle]") + return ..() return @@ -282,4 +298,8 @@ return burst_delay /obj/machinery/power/emitter/proc/get_emitter_beam() + if(anomalous) + var/obj/item/projectile/energy/anomaly/projectile = new /obj/item/projectile/energy/anomaly(get_turf(src)) + projectile.particle_type = particle + return projectile return new /obj/item/projectile/beam/emitter(get_turf(src)) diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index d68009bbf3..9c5ef5fbc6 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -335,3 +335,16 @@ /obj/item/projectile/energy/flash/flare flash_range = 2 hud_state = "grenade_dummy" + +/obj/item/projectile/energy/anomaly + name = "anomaly emitter projectile" + light_color = "#be008f" + icon_state = "purple_laser" + damage = 5 + speed = 1.6 + var/particle_type + +/obj/item/projectile/energy/anomaly/Initialize(mapload, particle) + . = ..() + if(particle) + particle_type = particle diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index 84eb0f7e17..bc885a955c 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -44,12 +44,12 @@ return FALSE var/obj/effect/anomaly/anomaly = new core.anomaly_type(get_turf(core)) - anomaly.stabilize() + anomaly.stabilize(releaser.will_anchor, releaser.has_core, releaser.gives_stats) if(!releaser.infinite) releaser.icon_state = releaser.used_icon_state releaser.used = TRUE - releaser.name = "used " + name + releaser.name = "used " + releaser.name qdel(src) return ..() diff --git a/code/modules/research/tg/designs/boards/science.dm b/code/modules/research/tg/designs/boards/science.dm index fbf9bb1adf..d9563771ee 100644 --- a/code/modules/research/tg/designs/boards/science.dm +++ b/code/modules/research/tg/designs/boards/science.dm @@ -205,3 +205,12 @@ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design_techweb/board/anomaly_harvester + name = "Anomaly Harvester" + id = "anomaly_harvester" + build_path = /obj/item/circuitboard/anomaly_harvester + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE diff --git a/code/modules/research/tg/designs/misc_designs.dm b/code/modules/research/tg/designs/misc_designs.dm index 44a5eaada2..bd53173d1b 100644 --- a/code/modules/research/tg/designs/misc_designs.dm +++ b/code/modules/research/tg/designs/misc_designs.dm @@ -289,8 +289,20 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE +/datum/design_techweb/sci_anomaly_releaser + name = "Scientific Anomaly Releaser" + desc = "A one-time use device, reactivates anomalies and stabilizes them, but leaves no core behind." + id = "sci_anomaly_releaser" + build_type = PROTOLATHE + materials = list(MAT_STEEL = SHEET_MATERIAL_AMOUNT, MAT_PHORON = SHEET_MATERIAL_AMOUNT * 1.5) + build_path = /obj/item/anomaly_releaser/science + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SCIENCE + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + /datum/design_techweb/anomaly_releaser - name = "Anomaly Releaser" + name = "Advanced Anomaly Releaser" desc = "A one-time use device, reactivates anomalies and stabilizes them." id = "anomaly_releaser" build_type = PROTOLATHE diff --git a/code/modules/research/tg/designs/prosfab_designs.dm b/code/modules/research/tg/designs/prosfab_designs.dm index 83a84b2bdb..dc80674309 100644 --- a/code/modules/research/tg/designs/prosfab_designs.dm +++ b/code/modules/research/tg/designs/prosfab_designs.dm @@ -702,3 +702,10 @@ id = "prosthetic_teshari" // req_tech = list(TECH_DATA = 3, TECH_BIO = 4) build_path = /obj/item/disk/species/teshari + +/datum/design_techweb/prosfab/robot_upgrade/advanced/anomgun + name = "Anomalous Particle Gun Module" + desc = "An anomalous particle gun, for anomaly harvesting." + id = "borg_anomgun_module" + materials = list(MAT_STEEL = 4000, MAT_GLASS = 3500, MAT_PHORON = 1500, MAT_DIAMOND = 1500, MAT_URANIUM = 1500) + build_path = /obj/item/borg/upgrade/restricted/anomalygun diff --git a/code/modules/research/tg/designs/tool_designs.dm b/code/modules/research/tg/designs/tool_designs.dm index 18540c10bc..a0be2be50d 100644 --- a/code/modules/research/tg/designs/tool_designs.dm +++ b/code/modules/research/tg/designs/tool_designs.dm @@ -1015,3 +1015,14 @@ RND_CATEGORY_TOOLS ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_CARGO + +/datum/design_techweb/anomaly_scanner + name = "Anomaly Scanner" + id = "anomaly_scanner" + materials = list(MAT_STEEL = 1000, MAT_GLASS = 500) + build_path = /obj/item/anomaly_scanner + build_type = PROTOLATHE + category = list( + RND_CATEGORY_TOOLS + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE diff --git a/code/modules/research/tg/designs/weapon_designs.dm b/code/modules/research/tg/designs/weapon_designs.dm index b0a551dd29..8a02203730 100644 --- a/code/modules/research/tg/designs/weapon_designs.dm +++ b/code/modules/research/tg/designs/weapon_designs.dm @@ -960,3 +960,14 @@ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_RANGED ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design_techweb/anomaly_gun + name = "Handheld Anomalous Particle Releaser" + id = "anom_gun" + build_type = PROTOLATHE + materials = list(MAT_STEEL = 4000, MAT_GLASS = 3500, MAT_PHORON = 1500, MAT_DIAMOND = 1500, MAT_URANIUM = 1500) + build_path = /obj/item/gun/energy/anomaly + category = list( + RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_RANGED + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE diff --git a/code/modules/research/tg/experisci/experiment/instances/misc.dm b/code/modules/research/tg/experisci/experiment/instances/misc.dm index 906105e6c2..8a18e94b51 100644 --- a/code/modules/research/tg/experisci/experiment/instances/misc.dm +++ b/code/modules/research/tg/experisci/experiment/instances/misc.dm @@ -87,3 +87,19 @@ /obj/item/clothing/suit/bomb_suit = 1, /obj/item/clothing/suit/bio_suit = 1, ) + +/datum/experiment/scanning/points/anomaly + name = "Anomaly Research Scans" + description = "Anomalies. What are they? Where do they come from? Why are they here? Get close and personal with some of them, and observe their strange nature." + required_points = 4 + required_atoms = list( + /obj/effect/anomaly/bioscrambler = 1, + /obj/effect/anomaly/bluespace = 1, + /obj/effect/anomaly/dimensional = 1, + /obj/effect/anomaly/dust = 1, + /obj/effect/anomaly/flux = 1, + /obj/effect/anomaly/grav = 1, + /obj/effect/anomaly/hallucination = 1, + /obj/effect/anomaly/pyro = 1, + /obj/effect/anomaly/weather = 1, + ) diff --git a/code/modules/research/tg/techwebs/nodes/research_nodes.dm b/code/modules/research/tg/techwebs/nodes/research_nodes.dm index f91f4e8900..3ef323b425 100644 --- a/code/modules/research/tg/techwebs/nodes/research_nodes.dm +++ b/code/modules/research/tg/techwebs/nodes/research_nodes.dm @@ -99,24 +99,59 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS) announce_channels = list(CHANNEL_SCIENCE) +/datum/techweb_node/xenoarch + id = TECHWEB_NODE_XENOARCHEOLOGY + display_name = "Xenoarcheology Research" + description = "Researching those who came before us, extracting artifacts of great value, and harnessing their powers." + prereq_ids = list(TECHWEB_NODE_FUNDIMENTAL_SCI) + design_ids = list( + "ano_scanner", + "xenoarch_multitool", + "excavationdrill", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS) + announce_channels = list(CHANNEL_SCIENCE) + /datum/techweb_node/anomaly_research id = TECHWEB_NODE_ANOMALY_RESEARCH display_name = "Anomaly Research" description = "Delving into the study of mysterious anomalies to investigate methods to refine and harness their unpredictable energies." prereq_ids = list(TECHWEB_NODE_APPLIED_BLUESPACE) design_ids = list( - "xenoarch_multitool", - "excavationdrill", - "ano_scanner", - //"anomaly_refinery", "anomaly_neutralizer", - "anomaly_releaser", + "sci_anomaly_releaser", "reactive_armour", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS) announce_channels = list(CHANNEL_SCIENCE) -// Decided that we were not keen on this being able to be printed freely as we immediately saw undesirable behaviour //CHOMPEdit - Re-enable +/datum/techweb_node/anomaly_harvesting + id = TECHWEB_NODE_ANOMALY_HARVESTING + display_name = "Anomaly Harvesting" + description = "Harness the power of the mysterious anomalies, refining their energies into tangible materials." + prereq_ids = list(TECHWEB_NODE_ANOMALY_RESEARCH) + design_ids = list( + "anomaly_harvester", + "anomaly_scanner", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS) + announce_channels = list(CHANNEL_SCIENCE) + +/datum/techweb_node/applied_anomaly_harvesting + id = TECHWEB_NODE_APPLIED_ANOMALY_HARVESTING + display_name = "Applied Anomaly Harvesting" + description = "Advanced research in the anomaly field, allowing sophisticated tools to aid in their study." + prereq_ids = list(TECHWEB_NODE_ANOMALY_HARVESTING) + design_ids = list( + "anomaly_releaser", + "anom_gun", + "borg_anomgun_module" + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS) + announce_channels = list(CHANNEL_SCIENCE) + discount_experiments = list(/datum/experiment/scanning/points/anomaly = TECHWEB_TIER_3_POINTS) + +// Decided that we were not keen on this being able to be printed freely as we immediately saw undesirable behaviour // CHOMPEnable Start /datum/techweb_node/telekinetics id = TECHWEB_NODE_TELEKINETIC_RESEARCH display_name = "Applied Telekinetics Research" @@ -127,4 +162,4 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS) announce_channels = list(CHANNEL_SCIENCE) -// //CHOMPEdit - Re-enable +// CHOMPEnable End diff --git a/code/modules/telesci/telesci_computer.dm b/code/modules/telesci/telesci_computer.dm index 9aab06a9b4..05e708ce56 100644 --- a/code/modules/telesci/telesci_computer.dm +++ b/code/modules/telesci/telesci_computer.dm @@ -189,17 +189,10 @@ /obj/machinery/computer/telescience/proc/telefail() COOLDOWN_START(src, teleport_cooldown, (2 SECONDS)) switch(rand(99)) - if(0 to 80) + if(0 to 85) sparks() visible_message(span_warning("The telepad weakly fizzles.")) return - if(81 to 85) - sparks() - var/anomaly = pick(FLUX_ANOMALY, GRAVITATIONAL_ANOMALY, PYRO_ANOMALY, HALLUCINATION_ANOMALY, BIOSCRAMBLER_ANOMALY, DIMENSIONAL_ANOMALY, WEATHER_ANOMALY) - generate_anomaly(get_turf(telepad), anomaly, 1, FALSE) - for(var/mob/living/carbon/human/human in viewers(telepad, null)) - to_chat(human, span_warning("The telepad crackles with energy, as a tear in reality is created!")) - return if(86 to 90) // Irradiate everyone in telescience! for(var/obj/machinery/telepad/E in GLOB.machines) diff --git a/code/modules/xenoarcheaology/tools/suspension_generator.dm b/code/modules/xenoarcheaology/tools/suspension_generator.dm index c269f56f6a..214526e9da 100644 --- a/code/modules/xenoarcheaology/tools/suspension_generator.dm +++ b/code/modules/xenoarcheaology/tools/suspension_generator.dm @@ -1,6 +1,6 @@ /obj/machinery/suspension_gen name = "suspension field generator" - desc = "It has stubby bolts up against it's treads for stabilising. Used to be required for artifact removal but now merely works as a monster deterrant." + desc = "It has stubby bolts up against it's treads for stabilising. Used to hold anomalies stable in place." icon = 'icons/obj/xenoarchaeology.dmi' icon_state = "suspension" density = 1 @@ -8,7 +8,7 @@ var/obj/item/cell/cell var/obj/item/card/id/auth_card var/locked = 1 - var/power_use = 5 + var/power_use = 15 var/obj/effect/suspension_field/suspension_field /obj/machinery/suspension_gen/Initialize(mapload) @@ -153,6 +153,12 @@ M.Weaken(5) M.visible_message(span_blue("[icon2html(M,viewers(M))] [M] begins to float in the air!"),"You feel tingly and light, but it is difficult to move.") + for(var/obj/effect/anomaly/anom in T) + anom.immortal = TRUE + anom.move_chance = 0 + if(!anom.stats) + anom.stats = new /datum/anomaly_stats(anom) + suspension_field = new(T) visible_message(span_blue("[icon2html(src,viewers(src))] [src] activates with a low hum.")) icon_state = "suspension_on" @@ -181,6 +187,15 @@ to_chat(M, span_info("You no longer feel like floating.")) M.Weaken(3) + for(var/obj/effect/anomaly/anom in T) + if(anom.stats) + var/datum/anomaly_stats/anom_stats = anom.stats + if(istype(anom_stats.modifier, /datum/anomaly_modifiers/move)) + anom.move_chance = initial(anom.move_chance) + continue + else + anom.move_chance = initial(anom.move_chance) + visible_message(span_blue("[icon2html(src,viewers(src))] [src] deactivates with a gentle shudder.")) qdel(suspension_field) suspension_field = null diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 9855dbcdc2..62eeac68c5 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/icons/obj/devices/tool.dmi b/icons/obj/devices/tool.dmi index 0d89f29676..59ae27ca5b 100644 Binary files a/icons/obj/devices/tool.dmi and b/icons/obj/devices/tool.dmi differ diff --git a/icons/obj/machines/anomaly_harvester.dmi b/icons/obj/machines/anomaly_harvester.dmi new file mode 100644 index 0000000000..6c5b8a76f3 Binary files /dev/null and b/icons/obj/machines/anomaly_harvester.dmi differ diff --git a/maps/groundbase/gb-z2.dmm b/maps/groundbase/gb-z2.dmm index bc6ab3f3d6..9efe0bea88 100644 --- a/maps/groundbase/gb-z2.dmm +++ b/maps/groundbase/gb-z2.dmm @@ -6512,6 +6512,7 @@ /obj/machinery/requests_console/preset/research{ pixel_y = 30 }, +/obj/machinery/anomaly_harvester, /turf/simulated/floor/tiled, /area/groundbase/science/rnd) "rI" = ( @@ -7172,6 +7173,7 @@ /turf/simulated/floor/tiled/white, /area/groundbase/medical/triage) "tI" = ( +/obj/machinery/rnd/destructive_analyzer, /turf/simulated/floor/tiled/dark, /area/groundbase/science/rnd) "tJ" = ( @@ -10461,13 +10463,32 @@ /area/groundbase/dorms/room1) "Dm" = ( /obj/structure/table/standard, -/obj/item/retail_scanner/science, /obj/machinery/atmospherics/unary/vent_scrubber/on{ dir = 8 }, /obj/structure/noticeboard{ pixel_x = 32 }, +/obj/item/assembly/signaler/anomaly/flux{ + pixel_y = 13; + pixel_x = 7 + }, +/obj/item/anomaly_scanner{ + pixel_x = 2; + pixel_y = -2 + }, +/obj/item/anomaly_scanner{ + pixel_x = 5; + pixel_y = 3 + }, +/obj/item/anomaly_releaser/science{ + pixel_y = -2; + pixel_x = -5 + }, +/obj/item/anomaly_releaser/science{ + pixel_y = -5; + pixel_x = -5 + }, /turf/simulated/floor/tiled, /area/groundbase/science/rnd) "Dn" = ( @@ -11032,6 +11053,7 @@ dir = 8 }, /obj/machinery/light, +/obj/machinery/power/emitter, /turf/simulated/floor/tiled, /area/groundbase/science/rnd) "EN" = ( @@ -17896,6 +17918,7 @@ dir = 1; pixel_y = -30 }, +/obj/item/retail_scanner/science, /turf/simulated/floor/tiled, /area/groundbase/science/rnd) "Yc" = ( diff --git a/maps/stellar_delight/stellar_delight1.dmm b/maps/stellar_delight/stellar_delight1.dmm index 27c648a57a..0f6034c37e 100644 --- a/maps/stellar_delight/stellar_delight1.dmm +++ b/maps/stellar_delight/stellar_delight1.dmm @@ -1300,6 +1300,7 @@ /obj/structure/reagent_dispensers/acid{ pixel_x = 32 }, +/obj/machinery/power/emitter, /turf/simulated/floor/tiled/steel_grid, /area/rnd/research) "cx" = ( @@ -9831,6 +9832,8 @@ /obj/item/pen, /obj/item/packageWrap, /obj/item/packageWrap, +/obj/item/multitool/scioutpost, +/obj/item/retail_scanner/science, /turf/simulated/floor/tiled/steel_grid, /area/rnd/research) "tP" = ( @@ -11863,8 +11866,26 @@ "yq" = ( /obj/structure/table/standard, /obj/machinery/recharger, -/obj/item/retail_scanner/science, -/obj/item/multitool/scioutpost, +/obj/item/assembly/signaler/anomaly/flux{ + pixel_y = 13; + pixel_x = 7 + }, +/obj/item/anomaly_scanner{ + pixel_x = 5; + pixel_y = 3 + }, +/obj/item/anomaly_scanner{ + pixel_x = 2; + pixel_y = -2 + }, +/obj/item/anomaly_releaser/science{ + pixel_y = -5; + pixel_x = -5 + }, +/obj/item/anomaly_releaser/science{ + pixel_y = -2; + pixel_x = -5 + }, /turf/simulated/floor/tiled/steel_grid, /area/rnd/research) "yr" = ( @@ -11906,6 +11927,7 @@ /obj/machinery/disposal/wall{ dir = 1 }, +/obj/machinery/anomaly_harvester, /turf/simulated/floor/tiled/steel_grid, /area/rnd/research) "yw" = ( @@ -22240,6 +22262,7 @@ "UA" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, +/obj/machinery/rnd/destructive_analyzer, /turf/simulated/floor/tiled/dark, /area/rnd/research) "UB" = ( diff --git a/maps/tether/tether-01-surface1.dmm b/maps/tether/tether-01-surface1.dmm index 814db18360..7da3136b52 100644 --- a/maps/tether/tether-01-surface1.dmm +++ b/maps/tether/tether-01-surface1.dmm @@ -3101,6 +3101,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 5 }, +/obj/machinery/anomaly_harvester, /turf/simulated/floor/tiled, /area/rnd/hardstorage) "afh" = ( @@ -8602,6 +8603,7 @@ /area/tether/surfacebase/surface_one_hall) "aoR" = ( /obj/machinery/light/small, +/obj/machinery/power/emitter, /turf/simulated/floor/tiled, /area/rnd/hardstorage) "aoS" = ( @@ -28631,6 +28633,14 @@ /obj/machinery/disposal, /turf/simulated/floor/plating, /area/crew_quarters/sleep/maintDorm2) +"aYy" = ( +/obj/structure/table/rack/shelf/steel, +/obj/item/assembly/signaler/anomaly/flux{ + pixel_y = 13; + pixel_x = 7 + }, +/turf/simulated/floor/tiled, +/area/rnd/hardstorage) "aYz" = ( /obj/structure/sink{ pixel_y = 20 @@ -28640,6 +28650,18 @@ }, /turf/simulated/floor/plating, /area/crew_quarters/sleep/maintDorm2) +"aYA" = ( +/obj/structure/table/rack/shelf/steel, +/obj/item/anomaly_releaser/science{ + pixel_y = 2; + pixel_x = 3 + }, +/obj/item/anomaly_releaser/science{ + pixel_y = 1; + pixel_x = -3 + }, +/turf/simulated/floor/tiled, +/area/rnd/hardstorage) "aYB" = ( /obj/structure/disposalpipe/trunk{ dir = 4 @@ -28647,6 +28669,18 @@ /obj/machinery/disposal, /turf/simulated/floor/plating, /area/crew_quarters/sleep/maintDorm1) +"aYC" = ( +/obj/structure/table/rack/shelf/steel, +/obj/item/anomaly_scanner{ + pixel_x = 2; + pixel_y = -2 + }, +/obj/item/anomaly_scanner{ + pixel_x = 5; + pixel_y = 3 + }, +/turf/simulated/floor/tiled, +/area/rnd/hardstorage) "aYE" = ( /obj/structure/toilet{ pixel_y = 10 @@ -45942,8 +45976,8 @@ auK avh abm abw -abw -abw +aYC +aYy abv afA abv @@ -46227,7 +46261,7 @@ avh abm abw abw -abw +aYA adM afU abI diff --git a/tgui/packages/tgui/interfaces/AnomalyHarvester/Sample.tsx b/tgui/packages/tgui/interfaces/AnomalyHarvester/Sample.tsx new file mode 100644 index 0000000000..95a6a5cef9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AnomalyHarvester/Sample.tsx @@ -0,0 +1,62 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Button, DmIcon, Section, Stack } from 'tgui-core/components'; +import { capitalize } from 'tgui-core/string'; +import type { Data } from './types'; + +export const SampleDisplay = (props) => { + const { act, data } = useBackend(); + const { samples } = data; + + return ( +
act('release_all')} + > + Release All + + } + > + + {samples.length > 0 && + samples.map((sample) => { + return ( + + + + + + + + {capitalize(sample.name)} + + + + + + + + ); + })} + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AnomalyHarvester/index.tsx b/tgui/packages/tgui/interfaces/AnomalyHarvester/index.tsx new file mode 100644 index 0000000000..932222bc4d --- /dev/null +++ b/tgui/packages/tgui/interfaces/AnomalyHarvester/index.tsx @@ -0,0 +1,45 @@ +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { LabeledList, ProgressBar, Stack } from 'tgui-core/components'; +import { capitalize } from 'tgui-core/string'; +import { SampleDisplay } from './Sample'; +import type { Data } from './types'; + +export const AnomalyHarvester = (props) => { + const { act, data } = useBackend(); + const { points, pointsToGenerate, name } = data; + + const progress = (points / pointsToGenerate) * 100; + return ( + + + + + + + + + {!!name && {capitalize(name)}} + {`${progress.toFixed()}%`} + + + + + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AnomalyHarvester/types.ts b/tgui/packages/tgui/interfaces/AnomalyHarvester/types.ts new file mode 100644 index 0000000000..bd4531143e --- /dev/null +++ b/tgui/packages/tgui/interfaces/AnomalyHarvester/types.ts @@ -0,0 +1,13 @@ +export type Data = { + name: string | null; + points: number; + pointsToGenerate: number; + samples: Sample[]; +}; + +type Sample = { + name: string; + icon: string; + icon_state: string; + ref: string; +}; diff --git a/tgui/packages/tgui/interfaces/AnomalyScanner.tsx b/tgui/packages/tgui/interfaces/AnomalyScanner.tsx new file mode 100644 index 0000000000..fddc7bc0dc --- /dev/null +++ b/tgui/packages/tgui/interfaces/AnomalyScanner.tsx @@ -0,0 +1,104 @@ +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { LabeledList, NoticeBox, Section, Stack } from 'tgui-core/components'; +import { round } from 'tgui-core/math'; +import { capitalizeAll } from 'tgui-core/string'; + +type Data = { + anomaly_name: string; + severity: number; + stability: string; + point_output: number; + danger_type: string; + unstable_type: string; + containment_type: string; + transformation_type: string; + modifier?: string; + countdown: number; +}; + +const colorMap = { + Decaying: 'yellow', + Stable: 'green', +} as const; + +function getColor(stability: string): string { + return colorMap[stability] ?? 'bad'; +} + +export const AnomalyScanner = (props) => { + const { data } = useBackend(); + + const { + anomaly_name, + severity, + stability, + point_output, + danger_type, + unstable_type, + containment_type, + transformation_type, + modifier, + countdown, + theme, + } = data; + + return ( + + + {anomaly_name ? ( + + +
+ + + {severity}% + + + {stability} + + + {round(point_output, 0)} + + + {round(countdown, 0)} seconds + + +
+
+ +
+ + + {danger_type} + + + {unstable_type} + + + {containment_type} + + + {transformation_type} + + +
+
+ {modifier && ( + +
+ {modifier} +
+
+ )} +
+ ) : ( + No anomaly scanned + )} +
+
+ ); +}; diff --git a/vorestation.dme b/vorestation.dme index dfc17497ee..bb39e1de6d 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -1637,6 +1637,8 @@ #include "code\game\objects\effects\anomalies\anomalies_weather_themes.dm" #include "code\game\objects\effects\anomalies\anomaly_placer.dm" #include "code\game\objects\effects\anomalies\anomaly_spawner.dm" +#include "code\game\objects\effects\anomalies\anomaly_stats.dm" +#include "code\game\objects\effects\anomalies\anomaly_stats_mods.dm" #include "code\game\objects\effects\chem\chemsmoke.dm" #include "code\game\objects\effects\chem\coating.dm" #include "code\game\objects\effects\chem\foam.dm" @@ -1721,7 +1723,6 @@ #include "code\game\objects\items\devices\advnifrepair.dm" #include "code\game\objects\items\devices\ai_detector.dm" #include "code\game\objects\items\devices\aicard.dm" -#include "code\game\objects\items\devices\anomaly.dm" #include "code\game\objects\items\devices\binoculars.dm" #include "code\game\objects\items\devices\body_snatcher_vr.dm" #include "code\game\objects\items\devices\chameleonproj.dm" @@ -1893,6 +1894,7 @@ #include "code\game\objects\items\weapons\circuitboards\computer\shuttle.dm" #include "code\game\objects\items\weapons\circuitboards\computer\supply.dm" #include "code\game\objects\items\weapons\circuitboards\computer\telecomms.dm" +#include "code\game\objects\items\weapons\circuitboards\machinery\anomaly_harvester.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\biogenerator.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\cloning.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\engineering.dm" @@ -2377,6 +2379,8 @@ #include "code\modules\alarm\fire_alarm.dm" #include "code\modules\alarm\motion_alarm.dm" #include "code\modules\alarm\power_alarm.dm" +#include "code\modules\anomalies\anomaly.dm" +#include "code\modules\anomalies\anomaly_harvester.dm" #include "code\modules\artifice\cursedform.dm" #include "code\modules\artifice\deadringer.dm" #include "code\modules\artifice\telecube.dm"