diff --git a/code/__defines/anomaly.dm b/code/__defines/anomaly.dm index 4071ccec8c3..2da7c5aea61 100644 --- a/code/__defines/anomaly.dm +++ b/code/__defines/anomaly.dm @@ -45,3 +45,4 @@ #define BIOSCRAMBLER_ANOMALY "bioscrambler_anomaly" #define HALLUCINATION_ANOMALY "hallucination_anomaly" #define DIMENSIONAL_ANOMALY "dimensional_anomaly" +#define WEATHER_ANOMALY "weather_anomaly" diff --git a/code/controllers/subsystems/research.dm b/code/controllers/subsystems/research.dm index 78db93a99d6..d086c7122c1 100644 --- a/code/controllers/subsystems/research.dm +++ b/code/controllers/subsystems/research.dm @@ -51,7 +51,8 @@ SUBSYSTEM_DEF(research) /obj/item/assembly/signaler/anomaly/flux = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), /obj/item/assembly/signaler/anomaly/grav = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), /obj/item/assembly/signaler/anomaly/hallucination = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), - /obj/item/assembly/signaler/anomaly/pyro = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) + /obj/item/assembly/signaler/anomaly/pyro = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS), + /obj/item/assembly/signaler/anomaly/weather = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) ) ///Allows repeated deconstruction of these items for points. These items MUST be in techweb_point_items as well. var/list/techweb_repeatable_items = list( diff --git a/code/game/objects/effects/anomalies/_anomalies.dm b/code/game/objects/effects/anomalies/_anomalies.dm index 4d0396369ff..0304add1f08 100644 --- a/code/game/objects/effects/anomalies/_anomalies.dm +++ b/code/game/objects/effects/anomalies/_anomalies.dm @@ -80,7 +80,7 @@ return FALSE /obj/effect/anomaly/proc/anomalyNeutralize() - new /obj/effect/effect/smoke/bad(loc) + new /obj/effect/effect/smoke(loc) if(!isnull(anomaly_core)) anomaly_core.forceMove(get_turf(src)) anomaly_core = null @@ -101,21 +101,23 @@ return TRUE return ..() -/proc/generate_anomaly(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5, has_changed_lifespan = TRUE) +/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) return switch(type) if(FLUX_ANOMALY) var/explosive = has_changed_lifespan ? FLUX_NO_EMP : FLUX_LIGHT_EMP - new /obj/effect/anomaly/flux(local_turf, has_changed_lifespan ? rand(25 SECONDS, 35 SECONDS) : null, FALSE, explosive) + new /obj/effect/anomaly/flux(local_turf, has_changed_lifespan ? rand(25 SECONDS, 35 SECONDS) : null, drops_core, explosive) if(GRAVITATIONAL_ANOMALY) - new /obj/effect/anomaly/grav(local_turf, has_changed_lifespan ? rand(20 SECONDS, 30 SECONDS) : null, FALSE) + new /obj/effect/anomaly/grav(local_turf, has_changed_lifespan ? rand(20 SECONDS, 30 SECONDS) : null, drops_core) if(PYRO_ANOMALY) - new /obj/effect/anomaly/pyro(local_turf, has_changed_lifespan ? rand(15 SECONDS, 25 SECONDS) : null, FALSE) + new /obj/effect/anomaly/pyro(local_turf, has_changed_lifespan ? rand(15 SECONDS, 25 SECONDS) : null, drops_core) if(HALLUCINATION_ANOMALY) - new /obj/effect/anomaly/hallucination/supermatter(local_turf, has_changed_lifespan ? rand(15 SECONDS, 25 SECONDS) : null, FALSE) + new /obj/effect/anomaly/hallucination/supermatter(local_turf, has_changed_lifespan ? rand(15 SECONDS, 25 SECONDS) : null, drops_core) if(BIOSCRAMBLER_ANOMALY) - new /obj/effect/anomaly/bioscrambler/docile(local_turf, null, FALSE) + new /obj/effect/anomaly/bioscrambler/docile(local_turf, null, drops_core) if(DIMENSIONAL_ANOMALY) - new /obj/effect/anomaly/dimensional(local_turf, null, FALSE) + new /obj/effect/anomaly/dimensional(local_turf, null, drops_core) + if(WEATHER_ANOMALY) + new /obj/effect/anomaly/dimensional(local_turf, null, drops_core) diff --git a/code/game/objects/effects/anomalies/anomalies_weather.dm b/code/game/objects/effects/anomalies/anomalies_weather.dm new file mode 100644 index 00000000000..04ae98dbeb2 --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_weather.dm @@ -0,0 +1,121 @@ +/obj/effect/anomaly/weather + name = "weather anomaly" + icon_state = "weather" + anomaly_core = /obj/item/assembly/signaler/anomaly/weather + lifespan = ANOMALY_COUNTDOWN_TIMER * 2.5 + var/telegraph_percent = 7 + + var/list/area/affected_areas = list() + var/list/turf/affected_turfs = list() + + var/datum/anomalous_weather/selected_weather + + var/is_raining = FALSE + +/obj/effect/anomaly/weather/Initialize(mapload, new_lifespan, drops_core) + . = ..() + + affected_areas.Add(impact_area) + + if(!selected_weather) + pick_weather() + + var/telegraph = lifespan / telegraph_percent + + for(var/spread_dir in GLOB.alldirs) + var/area/nearby = find_adjacent_impacted_area(spread_dir) + if(isnull(nearby) || nearby.flag_check(AREA_FORBID_EVENTS)) + continue + if(istype(nearby, /area/space)) + continue + affected_areas |= nearby + + for(var/area/area in affected_areas) + for(var/mob/mob in area) + to_chat(mob, span_notice(selected_weather.telegraph_message)) + for(var/turf/turf in area) + affected_turfs.Add(turf) + + apply_wibbly_filters(src) + + addtimer(CALLBACK(src, PROC_REF(start_weather)), telegraph, TIMER_DELETE_ME) + +/obj/effect/anomaly/weather/proc/find_adjacent_impacted_area(check_dir) + var/limit = 10 + var/turf/next_turf = get_step(src, check_dir) + while(next_turf.loc == impact_area && limit > 0) + next_turf = get_step(next_turf, check_dir) + if(isnull(next_turf)) + return null + limit -= 1 + + return next_turf.loc + +/obj/effect/anomaly/weather/anomalyNeutralize() + clear_weather() + return ..() + +/obj/effect/anomaly/weather/detonate() + new /obj/effect/effect/smoke/bad/burntfood(loc) // OOoooOooh spooky cloud... Doesn't do ANYTHING + qdel(src) + +/obj/effect/anomaly/weather/anomalyEffect(seconds_per_tick) + ..() + if(!is_raining) + return + + for(var/turf/turf in affected_turfs) + selected_weather.affect_turf(turf) + + for(var/mob/mob as anything in GLOB.player_list) + if(get_area(mob) in affected_areas) + selected_weather.hear_sounds(mob, TRUE) + else + selected_weather.hear_sounds(mob, FALSE) + +/obj/effect/anomaly/weather/proc/pick_weather(new_weather_path) + if(!new_weather_path) + new_weather_path = pick(subtypesof(/datum/anomalous_weather)) + + selected_weather = new new_weather_path + +/obj/effect/anomaly/weather/proc/start_weather() + if(QDELETED(src)) + return + + is_raining = TRUE + + if(selected_weather.loop_sounds) + selected_weather.loop_sounds.start() + + for(var/turf/area_turf in affected_turfs) + apply_to_turf(area_turf) + +/obj/effect/anomaly/weather/proc/clear_weather() + if(!is_raining) + return + + if(selected_weather.sounds) + selected_weather.loop_sounds.stop() + + for(var/area in affected_areas) + for(var/turf/area_turf in area) + remove_from_turf(area_turf) + for(var/mob/mob in area) + selected_weather.hear_sounds(mob, FALSE) + +/obj/effect/anomaly/weather/proc/apply_to_turf(turf/T) + if(selected_weather.visuals in T.vis_contents) + WARNING("Was asked to add weather to [T.x], [T.y], [T.z] despite already having us in it's vis contents") + return + T.vis_contents += selected_weather.visuals + +/obj/effect/anomaly/weather/proc/remove_from_turf(turf/T) + if(!(selected_weather.visuals in T.vis_contents)) + WARNING("Was asked to remove weather from [T.x], [T.y], [T.z] despite it not having us in it's vis contents") + return + T.vis_contents -= selected_weather.visuals + +/obj/effect/anomaly/weather/Destroy() + clear_weather() + . = ..() diff --git a/code/game/objects/effects/anomalies/anomalies_weather_themes.dm b/code/game/objects/effects/anomalies/anomalies_weather_themes.dm new file mode 100644 index 00000000000..77e15a58d2f --- /dev/null +++ b/code/game/objects/effects/anomalies/anomalies_weather_themes.dm @@ -0,0 +1,188 @@ +/datum/anomalous_weather + var/name = "Unnamed Weather" + var/icon = 'icons/effects/weather.dmi' + var/icon_state = null + var/reagent_id + var/weather_colour + + var/datum/reagents/reagent_holder + var/atom/movable/weather_visuals/visuals = null + + var/telegraph_message = "The storm is coming..." + var/patter_message = "Something patters softly onto your umbrella. You're not quite sure of what it is, actually." + var/drench_message = "You're getting... Wet?" + + var/sounds = null + var/datum/looping_sound/loop_sounds = null + +/datum/anomalous_weather/New() + ..() + visuals = new() + + visuals.icon = icon + visuals.icon_state = icon_state + + loop_sounds = new sounds(list(), FALSE, TRUE) + + if(reagent_id) + reagent_holder = new(10000, null) + reagent_holder.add_reagent(reagent_id, 10000) + weather_colour = reagent_holder.get_color() + + if(weather_colour) + visuals.color = weather_colour + +/datum/anomalous_weather/proc/affect_turf(turf/to_affect) + if(iswall(to_affect)) + return + + for(var/atom/thing in to_affect) + if(isliving(thing)) + affect_mob(thing) + continue + + if(!reagent_id) // No reagent was given, so don't do anything past this. + continue + + reagent_holder.touch_obj(thing, 2) + + if(!istype(thing, /obj/item/reagent_containers)) + continue + + var/obj/item/reagent_containers/container = thing + if(!container.is_open_container() || container.reagents.total_volume >= container.reagents.maximum_volume) + continue + + container.reagents.add_reagent(reagent_id, 2) + + if(reagent_id == REAGENT_ID_WATER) + to_affect.wash(CLEAN_ALL) + + do_special(to_affect) + + return + +/datum/anomalous_weather/proc/affect_mob(mob/living/affected_mob) + var/obj/item/melee/umbrella/U = affected_mob.get_active_hand() + + if(!istype(U) || !U.open) + U = affected_mob.get_inactive_hand() + + if(istype(U) && U.open) + if(prob(5)) + to_chat(affected_mob, span_notice(patter_message)) + return + + if(prob(5)) + to_chat(affected_mob, span_danger(drench_message)) + + if(!reagent_id) // No reagent was given, no need to continue. + return + + reagent_holder.splash_mob(affected_mob, 2) + + if(reagent_id == REAGENT_ID_WATER) + affected_mob.wash(CLEAN_ALL) + affected_mob.water_act(2) + +/datum/anomalous_weather/proc/do_special(var/turf/simulated/T) + return + +/datum/anomalous_weather/proc/hear_sounds(mob/M, adding) + if(!sounds) + return + if(adding) + loop_sounds.output_atoms |= M + return + loop_sounds.output_atoms -= M + +/datum/anomalous_weather/rain + name = "Rain" + icon_state = "anom_rain" + reagent_id = REAGENT_ID_WATER + sounds = /datum/looping_sound/weather/rain + telegraph_message = "Clouds begin to cover the ceiling of the area..." + patter_message = "Rain patters softly onto your umbrella." + drench_message = "Rain falls on you, drenching you in water." + +/datum/anomalous_weather/rain/do_special(turf/simulated/T) + if(prob(2)) + T.wet_floor(1) + +/datum/anomalous_weather/rain/blood + name = "Blood Rain" // From a lacerated sky + reagent_id = REAGENT_ID_BLOOD + telegraph_message = "Red clouds begin to cover the ceiling of the area..." + patter_message = "Blood splatters and falls past your umbrella." // Bleeding it's horrors + drench_message = "Blood splatters fall on you, covering you in it." + +/datum/anomalous_weather/rain/blood/do_special(turf/simulated/T) + if(prob(0.5)) + blood_splatter(T, null, TRUE) + +/datum/anomalous_weather/rain/storm + name = "Storm" + icon_state = "anom_rain_fast" + loop_sounds = /datum/looping_sound/weather/outside_blizzard + telegraph_message = "Grey clouds begin to cover the ceiling of the area..." + patter_message = "Rain patters continuously onto your umbrella." + drench_message = "Rain falls on you, completely soaking you." + +/datum/anomalous_weather/rain/storm/do_special(turf/simulated/T) + if(prob(3)) + T.wet_floor(1) + if(prob(0.025)) + lightning_strike(T) + +/datum/anomalous_weather/rain/acid + name = "Acid Rain" + reagent_id = REAGENT_ID_SACID + telegraph_message = "Strange clouds begin to cover the ceiling, an acidid tinge on them..." + +/datum/anomalous_weather/hail + name = "Hail" + icon_state = "snowfall_heavy_old" + reagent_id = REAGENT_ID_ICE + sounds = /datum/looping_sound/weather/outside_blizzard + telegraph_message = "Grey clouds begin to cover the ceiling of the area..." + patter_message = "Hail patters on your umbrella." + drench_message = "Hail pelts you" + +/datum/anomalous_weather/hail/affect_mob(mob/living/affected_mob) + ..() + + var/target_zone = pick(BP_ALL) + var/amount_blocked = affected_mob.run_armor_check(target_zone, "melee") + + var/damage = rand(1,3) + + if(amount_blocked >= 30) + return + + affected_mob.apply_damage(damage, BRUTE, target_zone, amount_blocked, used_weapon = "hail") + +/datum/anomalous_weather/ash_storm + name = "Ash Storm" + icon_state = "ashfall_moderate-alt" + sounds = /datum/looping_sound/weather/outside_blizzard + telegraph_message = "Ash clouds begin to cover the ceiling of the area..." + +/datum/anomalous_weather/ash_storm/affect_mob(mob/living/affected_mob) + affected_mob.inflict_heat_damage(rand(1, 3)) + +/datum/anomalous_weather/ash_storm/do_special(turf/simulated/T) + if(prob(5) && T.can_dirty) + T.dirt += 30 + +/* Activate this one for a lot of rain +/datum/anomalous_weather/rain/storm/cats_n_dogs + name = "Cats and dogs rain" + telegraph_message = "Strange, fuzzy clouds beging to cover the ceiling of the area..." + +/datum/anomalous_weather/rain/storm/cats_n_dogs/do_special(turf/simulated/T) + ..() + if(prob(0.01)) + var/mob/living/cat_or_dog = pick(/mob/living/simple_mob/animal/passive/cat, /mob/living/simple_mob/animal/passive/dog) + new cat_or_dog(T.loc) + T.visible_message(span_danger("A [cat_or_dog.name] falls from within the strange clouds!")) +*/ diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index ec65fbea6a5..01dc11ea182 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -13,7 +13,8 @@ //obj/effect/anomaly/bioscrambler = /obj/item/clothing/suit/armor/reactive/bioscrambling, /obj/effect/anomaly/hallucination = /obj/item/clothing/suit/armor/reactive/hallucinating, /obj/effect/anomaly/dimensional = /obj/item/clothing/suit/armor/reactive/barricade, - /obj/effect/anomaly/pyro = /obj/item/clothing/suit/armor/reactive/fire + /obj/effect/anomaly/pyro = /obj/item/clothing/suit/armor/reactive/fire, + /obj/effect/anomaly/weather = /obj/item/clothing/suit/armor/reactive/weather ) if(istype(I, /obj/item/assembly/signaler/anomaly)) @@ -301,6 +302,49 @@ reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return FALSE +/obj/item/clothing/suit/armor/reactive/weather + name = "reactive metereological armor" + desc = "An experimental suit of armor that manipulates the weather around the wearer when in danger." + emp_message = span_warning("The reactive armor's weather control unit sputters and groans...") + cooldown_message = span_danger("The reactive weather system is still recharging! It fails to activate!") + reactivearmor_cooldown_duration = 30 SECONDS + +/obj/item/clothing/suit/armor/reactive/weather/reactive_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("The reactive armor alters the weather around [owner], shielding [owner.p_them()] from [attack_text]!")) + playsound(src, 'sound/effects/lightningbolt.ogg', 33, TRUE) + + new /obj/effect/effect/smoke/bad(get_turf(loc)) + + var/list/affected_turfs = list() + for(var/mob/living/attacker in oview(2, owner)) + attacker.adjust_wet_stacks(5) + attacker.extinguish_mob() + + var/turf/location = get_turf(attacker) + if(!location.is_outdoors()) + continue + + for(var/turf/simulated/open/to_affect in view(1, attacker.loc)) + affected_turfs += to_affect + + shock_turf_windup(attacker.loc) + + reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration + return TRUE + +/obj/item/clothing/suit/armor/reactive/weather/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0) + owner.visible_message(span_danger("The reactive armor malfunctions, calling down a storm upon [owner.p_them()]!")) + playsound(src, 'sound/effects/lightningbolt.ogg', 33, TRUE) + + new /obj/effect/effect/smoke/bad(loc) + + if(!isopenturf(owner.loc)) + return + shock_turf_windup(owner.loc) + +/obj/item/clothing/suit/armor/reactive/weather/proc/shock_turf_windup(turf/target) + addtimer(CALLBACK(GLOBAL_PROC_REF(lightning_strike), target), 1 SECOND) + /obj/item/clothing/suit/armor/reactive/stealth name = "reactive stealth armor" desc = "An experimental suit of armor that renders the wearer invisible on detection of imminent harm, and creates a decoy that runs away from the owner. You can't fight what you can't see." diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index 85474342de4..d6840fd1ffb 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -94,3 +94,9 @@ 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 + +/obj/item/assembly/signaler/anomaly/weather + name = "\improper weather anomaly core" + desc = "The neutralized core of a weather anomaly. The sound of thunder can be heard in the distance. It'd probably be valuable for research." + icon_state = "weather_core" + anomaly_type = /obj/effect/anomaly/weather diff --git a/code/modules/telesci/telesci_computer.dm b/code/modules/telesci/telesci_computer.dm index 7c09f96b783..12a47218fce 100644 --- a/code/modules/telesci/telesci_computer.dm +++ b/code/modules/telesci/telesci_computer.dm @@ -195,7 +195,7 @@ return if(81 to 85) sparks() - var/anomaly = pick(FLUX_ANOMALY, GRAVITATIONAL_ANOMALY, PYRO_ANOMALY, HALLUCINATION_ANOMALY, BIOSCRAMBLER_ANOMALY, DIMENSIONAL_ANOMALY) + 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!")) diff --git a/icons/effects/anomalies.dmi b/icons/effects/anomalies.dmi index e8eb3b48441..b40d14d3897 100644 Binary files a/icons/effects/anomalies.dmi and b/icons/effects/anomalies.dmi differ diff --git a/icons/effects/weather.dmi b/icons/effects/weather.dmi index 5d58f27e364..1fa2c5e1a4f 100644 Binary files a/icons/effects/weather.dmi and b/icons/effects/weather.dmi differ diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi index f4f74333221..7e0d1801678 100644 Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ diff --git a/vorestation.dme b/vorestation.dme index cb409614c9b..c2d62504013 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -1574,6 +1574,8 @@ #include "code\game\objects\effects\anomalies\anomalies_gravity.dm" #include "code\game\objects\effects\anomalies\anomalies_hallucination.dm" #include "code\game\objects\effects\anomalies\anomalies_pyroclastic.dm" +#include "code\game\objects\effects\anomalies\anomalies_weather.dm" +#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\chem\chemsmoke.dm"