From 42543ac1415c0ace86f8ca44cde96e4a787c96a3 Mon Sep 17 00:00:00 2001 From: Time-Green <7501474+Time-Green@users.noreply.github.com> Date: Fri, 21 Jul 2023 17:51:15 +0200 Subject: [PATCH] NEW STATION TRAIT: Radioactive Nebula (#76825) ## About The Pull Request Adds a new station trait: Radioactive Nebula! The station is located inside a radioactive nebula. Space background and lighting is different shades of green. Objects in space will also glow green. (This is kinda lying, since the glowing stuff isn't radioactive, you just get an element that slowly irradiates you, though people and certain objects that get the 'IRRADIATED' status may still double-whammy you) Do not go into space without rad-protected gear, or you will get very sick very fast. RAD-protection MODsuit modules spawn in robotics and are also immediately researched. The nebula does protect against external threats, like pirates, ninja's and nukies. They can still get to the station pretty well, but they can't stay in space for extended periods of time To make it more livable, public rad protection gear will spawn in lockers around the station. Everyone will also spawn with potassium iodide pills in their emergency box. Dynamics threat is also reduced by 30, so there's a proclivity towards more lower threat rounds when the radioactive nebula is present. Radioactive resonance virus cannot be generated though, since it kinda obliterates any and all challenge and threat ![image](https://github.com/tgstation/tgstation/assets/7501474/8eedcb85-1bb8-4e87-a794-d6781fee680d) **Shielding** ![image](https://github.com/tgstation/tgstation/assets/7501474/ae1b25d7-6fbd-4a86-8c95-5947b1122632) In order to protect the station from radiation, nebula shielding units need to be constructed. Five spawn ready-to-built in engineering, and more can be bought pretty cheap from cargo. (Normal radstorms are disabled) The gravity generator has 20 minutes of innate shielding, where every nebula shielding unit adds another 20 minutes. 5 are needed to completely block all radiation even when the gravity gen is down, but constructing more is recommended in-case of sabotage/destructions/power outtages. Active nebula shielding will passively generate tritium. You can either vent/ignore this, or use it for something. I'm not an atmos tech but I'm sure you can do something with it _What happens when no shielding units are constructed/they all fail?_ The station will suffer a 5 minute long radiation storm, with only shuttles being excempt. The storm is nerfed strongly, and you can tank the 5 minutes, but you'll be pretty sick. After the 5 minutes are over, central command will send an emergency shielding unit which will block the radiation for 10 minutes and warn the station to set up nebula shielding. ## Why It's Good For The Game The station being inside a radioactive nebula shakes up a pretty major aspect of the game (that being the 'space' in space station 13). Hallway decals are colored green, display screens will display radiation markings, carps blend with the nebula, etc. Putting the station inside a radioactive nebula shakes up the rules of the game and what people can expect. Suddenly, you can no longer just go outside without taking meds or getting proper radiation protection, encouraging people to stay cozy and inside. ![image](https://github.com/tgstation/tgstation/assets/7501474/40112936-8514-47f7-b3e0-b1c782b6a0a6) Inside, the crew gets the goal to set-up radiation shielding to defend themselves against the nebula, rewarding a creative engineering department with passive resource income and protecting the station against massive radiation storms. I think it's nice to give engineering something to set up. Even if they don't care, they can just plop it down somewhere in a closed room and be done with it. The radiation storm is pretty aggressive, but very survivable if you use your potassium iodide pills, the extra radiation suits or whatever chemistry has whipped up. Most importantly, it gives the entire station a common enemy: the nebula. Everyone is encouraged to prepare against the mechanics. Chemistry can make meds, viro can make protective virusses, robotics gets encouraged to make radprotected MODsuits, engineering gets to set-up radiation shielding, assistants can look at space or whatever assistants do.
Cool images ![image](https://github.com/tgstation/tgstation/assets/7501474/387f2d75-8ba6-425b-8f0f-9423cf7aab19) ![image](https://github.com/tgstation/tgstation/assets/7501474/eb65de97-13ce-4ce9-8902-4144994d217f) ![image](https://github.com/tgstation/tgstation/assets/7501474/60b915a5-8549-4a3b-afe6-f0e823207883) ![image](https://github.com/tgstation/tgstation/assets/7501474/239ae614-8d26-4b77-936f-1d2baa38d32c)
## Changelog :cl: add: Adds a new rare radioactive nebula station trait! Get ready and PREPARE, before it gets in... tweak: Nearstation space area lighting may look slightly different /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Co-authored-by: Jacquerel --- code/__DEFINES/colors.dm | 1 + code/__DEFINES/dcs/signals/signals_area.dm | 2 + code/__DEFINES/radiation.dm | 2 + code/__DEFINES/station.dm | 9 +- code/__DEFINES/time.dm | 3 + code/__DEFINES/traits.dm | 1 + code/_globalvars/lists/color.dm | 20 ++ code/controllers/subsystem/atoms.dm | 5 +- code/controllers/subsystem/parallax.dm | 3 +- .../subsystem/processing/station.dm | 7 + code/controllers/subsystem/weather.dm | 4 + code/datums/diseases/advance/advance.dm | 2 +- code/datums/diseases/advance/symptoms/heal.dm | 3 + .../diseases/advance/symptoms/symptoms.dm | 4 + code/datums/elements/radioactive.dm | 34 ++- code/datums/materials/_material.dm | 10 +- code/datums/materials/basemats.dm | 1 - code/datums/station_traits/_station_trait.dm | 24 +- code/datums/station_traits/negative_traits.dm | 258 +++++++++++++++++- code/datums/station_traits/positive_traits.dm | 14 +- .../weather/weather_types/radiation_storm.dm | 37 ++- code/game/area/areas/misc.dm | 3 - code/game/gamemodes/dynamic/dynamic.dm | 27 ++ code/game/gamemodes/dynamic/readme.md | 5 + code/game/machinery/nebula_shielding.dm | 129 +++++++++ code/game/objects/effects/decals/decal.dm | 7 +- .../machines/machine_circuitboards.dm | 10 + .../objects/items/storage/boxes/job_boxes.dm | 3 + .../crates_lockers/closets/utility_closets.dm | 4 + code/game/turfs/open/floor/glass.dm | 6 +- code/game/turfs/open/space/space.dm | 5 +- code/modules/cargo/packs/_packs.dm | 15 + code/modules/cargo/packs/engineering.dm | 22 ++ code/modules/cargo/packs/materials.dm | 8 - code/modules/lighting/lighting_area.dm | 2 +- code/modules/lighting/static_lighting_area.dm | 2 +- .../mob/living/basic/space_fauna/carp/carp.dm | 21 +- code/modules/power/gravitygenerator.dm | 9 + code/modules/research/techweb/all_nodes.dm | 6 + config/dynamic.json | 5 + icons/effects/weather_effects.dmi | Bin 27717 -> 27720 bytes icons/obj/machines/nebula_shielding.dmi | Bin 0 -> 2779 bytes tgstation.dme | 1 + 43 files changed, 664 insertions(+), 70 deletions(-) create mode 100644 code/game/machinery/nebula_shielding.dm create mode 100644 icons/obj/machines/nebula_shielding.dmi diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index fbc12c60bd8..76d1ef360c4 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -323,6 +323,7 @@ #define COLOR_CARP_PALE_GREEN "#7ef099" #define COLOR_CARP_SILVER "#fdfbf3" #define COLOR_CARP_DARK_BLUE "#3a384d" +#define COLOR_CARP_DARK_GREEN "#358102" #define COLOR_GNOME_RED_ONE "#f10b0b" #define COLOR_GNOME_RED_TWO "#bc5347" diff --git a/code/__DEFINES/dcs/signals/signals_area.dm b/code/__DEFINES/dcs/signals/signals_area.dm index df5104cd885..0dc275f70e1 100644 --- a/code/__DEFINES/dcs/signals/signals_area.dm +++ b/code/__DEFINES/dcs/signals/signals_area.dm @@ -12,6 +12,8 @@ #define COMSIG_ENTER_AREA "enter_area" ///from base of area/Exited(): (area). Sent to "area-sensitive" movables, see __DEFINES/traits.dm for info. #define COMSIG_EXIT_AREA "exit_area" +///from base of /datum/controller/subsystem/atoms/proc/InitAtom(): (atom/new_atom) +#define COMSIG_AREA_INITIALIZED_IN "area_initialized_in" // Alarm listener datum signals ///Sent when an alarm is fired and an alarm listener has tracked onto it (alarm, area/source_area) diff --git a/code/__DEFINES/radiation.dm b/code/__DEFINES/radiation.dm index bb345312ce5..2ab5860763a 100644 --- a/code/__DEFINES/radiation.dm +++ b/code/__DEFINES/radiation.dm @@ -42,6 +42,8 @@ Ask Mothblocks if they're around /// The minimum exposure time before uranium structures can irradiate #define URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME (3 SECONDS) +/// The minimum exposure time before the radioactive nebula can irradiate +#define NEBULA_RADIATION_MINIMUM_EXPOSURE_TIME (10 SECONDS) /// Return values of [proc/get_perceived_radiation_danger] // If you change these, update /datum/looping_sound/geiger as well. diff --git a/code/__DEFINES/station.dm b/code/__DEFINES/station.dm index 5b97a079c6e..3cb026e4f76 100644 --- a/code/__DEFINES/station.dm +++ b/code/__DEFINES/station.dm @@ -2,8 +2,15 @@ #define STATION_TRAIT_NEUTRAL 2 #define STATION_TRAIT_NEGATIVE 3 - +/// For traits that shouldn't be selected, like abstract types (wow) #define STATION_TRAIT_ABSTRACT (1<<0) +/// Only run on planet stations +#define STATION_TRAIT_PLANETARY (1<<1) +/// Only run on space stations +#define STATION_TRAIT_SPACE_BOUND (1<<2) + +/// Not restricted by space or planet, can always just happen +#define STATION_TRAIT_MAP_UNRESTRICTED STATION_TRAIT_PLANETARY | STATION_TRAIT_SPACE_BOUND /// The data file that future station traits forced by an admin are stored in #define FUTURE_STATION_TRAITS_FILE "data/future_station_traits.json" diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm index 64955e4a569..00a85d61995 100644 --- a/code/__DEFINES/time.dm +++ b/code/__DEFINES/time.dm @@ -4,6 +4,9 @@ ///displays the current time into the round, with a lot of extra code just there for ensuring it looks okay after an entire day passes #define ROUND_TIME(...) ( "[world.time - SSticker.round_start_time > MIDNIGHT_ROLLOVER ? "[round((world.time - SSticker.round_start_time)/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]" ) +///Returns the time that has passed since the game started +#define STATION_TIME_PASSED(...) (world.time - SSticker.round_start_time) + /// Define that just has the current in-universe year for use in whatever context you might want to display that in. (For example, 2022 -> 2562 given a 540 year offset) #define CURRENT_STATION_YEAR (GLOB.year_integer + STATION_YEAR_OFFSET) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index d7d134e00ac..e1a6fdaef76 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -1102,6 +1102,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define STATION_TRAIT_BIRTHDAY "station_trait_birthday" #define STATION_TRAIT_SPIDER_INFESTATION "station_trait_spider_infestation" #define STATION_TRAIT_REVOLUTIONARY_TRASHING "station_trait_revolutionary_trashing" +#define STATION_TRAIT_RADIOACTIVE_NEBULA "station_trait_radioactive_nebula" ///From the market_crash event #define MARKET_CRASH_EVENT_TRAIT "crashed_market_event" diff --git a/code/_globalvars/lists/color.dm b/code/_globalvars/lists/color.dm index 26c0d89b9f1..1a195798f46 100644 --- a/code/_globalvars/lists/color.dm +++ b/code/_globalvars/lists/color.dm @@ -5,3 +5,23 @@ GLOBAL_LIST_INIT(contrast_colors, list( COLOR_TAN_ORANGE, COLOR_VIOLET, )) + +/// Weighted list of colours a carp can be +/// Weighted list of usual carp colors +GLOBAL_LIST_INIT(carp_colors, list( + COLOR_CARP_PURPLE = 7, + COLOR_CARP_PINK = 7, + COLOR_CARP_GREEN = 7, + COLOR_CARP_GRAPE = 7, + COLOR_CARP_SWAMP = 7, + COLOR_CARP_TURQUOISE = 7, + COLOR_CARP_BROWN = 7, + COLOR_CARP_TEAL = 7, + COLOR_CARP_LIGHT_BLUE = 7, + COLOR_CARP_RUSTY = 7, + COLOR_CARP_RED = 7, + COLOR_CARP_YELLOW = 7, + COLOR_CARP_BLUE = 7, + COLOR_CARP_PALE_GREEN = 7, + COLOR_CARP_SILVER = 1, // The rare silver carp +)) diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index 5e2bfb58ef6..9d639a703d9 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -155,10 +155,13 @@ SUBSYSTEM_DEF(atoms) BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT else SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) - var/atom/movable/location = A.loc + var/atom/location = A.loc if(location) /// Sends a signal that the new atom `src`, has been created at `loc` SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1]) + var/area/atom_area = get_area(location) + if(atom_area) + SEND_SIGNAL(atom_area, COMSIG_AREA_INITIALIZED_IN, A) if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables created_atoms += A.get_all_contents() diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm index 4cc105ce6c1..1a597de84fb 100644 --- a/code/controllers/subsystem/parallax.dm +++ b/code/controllers/subsystem/parallax.dm @@ -10,17 +10,16 @@ SUBSYSTEM_DEF(parallax) var/random_layer var/random_parallax_color - //These are cached per client so needs to be done asap so people joining at roundstart do not miss these. /datum/controller/subsystem/parallax/PreInit() . = ..() + if(prob(70)) //70% chance to pick a special extra layer random_layer = pick(/atom/movable/screen/parallax_layer/random/space_gas, /atom/movable/screen/parallax_layer/random/asteroids) random_parallax_color = pick(COLOR_TEAL, COLOR_GREEN, COLOR_SILVER, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)//Special color for random_layer1. Has to be done here so everyone sees the same color. planet_y_offset = rand(100, 160) planet_x_offset = rand(100, 160) - /datum/controller/subsystem/parallax/fire(resumed = FALSE) if (!resumed) src.currentrun = GLOB.clients.Copy() diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm index 21cd66c11a7..fc07b0acd28 100644 --- a/code/controllers/subsystem/processing/station.dm +++ b/code/controllers/subsystem/processing/station.dm @@ -58,6 +58,13 @@ PROCESSING_SUBSYSTEM_DEF(station) if(initial(trait_typepath.trait_flags) & STATION_TRAIT_ABSTRACT) continue //Dont add abstract ones to it + + if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_PLANETARY) && SSmapping.is_planetary()) // we're on a planet but we can't do planet ;_; + continue + + if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_SPACE_BOUND) && !SSmapping.is_planetary()) //we're in space but we can't do space ;_; + continue + selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight) var/positive_trait_count = pick(20;0, 5;1, 1;2) diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm index 386cd48a6bc..b78beeed815 100644 --- a/code/controllers/subsystem/weather.dm +++ b/code/controllers/subsystem/weather.dm @@ -81,3 +81,7 @@ SUBSYSTEM_DEF(weather) A = W break return A + +///Returns an active storm by its type +/datum/controller/subsystem/weather/proc/get_weather_by_type(type) + return locate(type) in processing diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index 99c3531d2ff..072b50c0d53 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -211,7 +211,7 @@ var/list/possible_symptoms = list() for(var/symp in SSdisease.list_symptoms) var/datum/symptom/S = new symp - if(S.naturally_occuring && S.level >= level_min && S.level <= level_max) + if(S.can_generate_randomly() && S.level >= level_min && S.level <= level_max) if(!HasSymptom(S)) possible_symptoms += S diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index 5c56cb3ad6e..1cd4a5c7a28 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -654,3 +654,6 @@ if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, BODYTYPE_ORGANIC)) M.update_damage_overlays() return 1 + +/datum/symptom/heal/radiation/can_generate_randomly() + return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_RADIOACTIVE_NEBULA) //because people can never really suffer enough diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm index 6b00d967192..28ba45f8de4 100644 --- a/code/datums/diseases/advance/symptoms/symptoms.dm +++ b/code/datums/diseases/advance/symptoms/symptoms.dm @@ -114,3 +114,7 @@ data["neutered"] = neutered data["threshold_desc"] = threshold_descs return data + +/// Check if we can generate randomly +/datum/symptom/proc/can_generate_randomly() + return naturally_occuring diff --git a/code/datums/elements/radioactive.dm b/code/datums/elements/radioactive.dm index e4e8059a7a4..c949692930c 100644 --- a/code/datums/elements/radioactive.dm +++ b/code/datums/elements/radioactive.dm @@ -1,20 +1,40 @@ #define DELAY_BETWEEN_RADIATION_PULSES (3 SECONDS) /// This atom will regularly pulse radiation. -/// As this is only applied on uranium objects for now, this defaults to uranium constants. /datum/element/radioactive - element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY | ELEMENT_BESPOKE + argument_hash_start_idx = 2 + ///Range of our wave in tiles + var/range + ///Threshold for radioactive permeance + var/threshold + ///Chance the object is irradiated + var/chance + ///Minimum time needed in order to be irradiated + var/minimum_exposure_time var/list/radioactive_objects = list() /datum/element/radioactive/New() START_PROCESSING(SSdcs, src) -/datum/element/radioactive/Attach(datum/target) +/datum/element/radioactive/Attach(\ + datum/target, \ + range = 3, \ + threshold = RAD_LIGHT_INSULATION, \ + chance = URANIUM_IRRADIATION_CHANCE, \ + minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,\ +) + . = ..() radioactive_objects[target] = world.time + src.range = range + src.threshold = threshold + src.chance = chance + src.minimum_exposure_time = minimum_exposure_time + /datum/element/radioactive/Detach(datum/source, ...) radioactive_objects -= source @@ -27,10 +47,10 @@ radiation_pulse( radioactive_object, - max_range = 3, - threshold = RAD_LIGHT_INSULATION, - chance = URANIUM_IRRADIATION_CHANCE, - minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME, + max_range = range, + threshold = threshold, + chance = chance, + minimum_exposure_time = minimum_exposure_time, ) radioactive_objects[radioactive_object] = world.time diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm index 5b07d0fec9f..d91884d972e 100644 --- a/code/datums/materials/_material.dm +++ b/code/datums/materials/_material.dm @@ -21,8 +21,8 @@ Simple datum which is instanced once per type and is used for every object of sa ///Base alpha of the material, is used for greyscale icons. var/alpha = 255 ///Starlight color of the material - ///This is the color of light it'll emit if its turf is transparent and over space - var/starlight_color = COLOR_STARLIGHT + ///This is the color of light it'll emit if its turf is transparent and over space. Defaults to COLOR_STARLIGHT if not set + var/starlight_color ///Bitflags that influence how SSmaterials handles this material. var/init_flags = MATERIAL_INIT_MAPLOAD ///Materials "Traits". its a map of key = category | Value = Bool. Used to define what it can be used for @@ -154,7 +154,11 @@ Simple datum which is instanced once per type and is used for every object of sa // We assume no parallax means no space means no light if(SSmapping.level_trait(on.z, ZTRAIT_NOPARALLAX)) return - on.set_light(2, 0.75, starlight_color) + on.set_light(2, 0.75, get_starlight_color()) + +///Gets the space color and possible changed color if space is different +/datum/material/proc/get_starlight_color() + return starlight_color || GLOB.starlight_color /datum/material/proc/get_greyscale_config_for(datum/greyscale_config/config_path) if(!config_path) diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm index d11a2c0e8d9..44061746eaf 100644 --- a/code/datums/materials/basemats.dm +++ b/code/datums/materials/basemats.dm @@ -18,7 +18,6 @@ desc = "Glass forged by melting sand." color = "#88cdf1" greyscale_colors = "#88cdf196" - starlight_color = COLOR_STARLIGHT alpha = 150 categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) integrity_modifier = 0.1 diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm index cb0de6afd4e..e42ce87f3e5 100644 --- a/code/datums/station_traits/_station_trait.dm +++ b/code/datums/station_traits/_station_trait.dm @@ -18,16 +18,23 @@ var/trait_to_give ///What traits are incompatible with this one? var/blacklist - ///Extra flags for station traits such as it being abstract - var/trait_flags + ///Extra flags for station traits such as it being abstract, planetary or space only + var/trait_flags = STATION_TRAIT_MAP_UNRESTRICTED /// Whether or not this trait can be reverted by an admin var/can_revert = TRUE + /// The ID that we look for in dynamic.json. Not synced with 'name' because I can already see this go wrong + var/dynamic_threat_id + /// If ran during dynamic, do we reduce the total threat? Will be overriden by config if set + var/threat_reduction = 0 /datum/station_trait/New() . = ..() RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(on_round_start)) + if(threat_reduction) + GLOB.dynamic_station_traits[src] = threat_reduction + if(trait_processes) START_PROCESSING(SSstation, src) if(trait_to_give) @@ -35,6 +42,7 @@ /datum/station_trait/Destroy() SSstation.station_traits -= src + GLOB.dynamic_station_traits.Remove(src) return ..() /// Proc ran when round starts. Use this for roundstart effects. @@ -55,3 +63,15 @@ REMOVE_TRAIT(SSstation, trait_to_give, STATION_TRAIT) qdel(src) + +///Called by decals if they can be colored, to see if we got some cool colors for them. Only takes the first station trait +/proc/request_station_colors(atom/thing_to_color, pattern = PATTERN_DEFAULT) + for(var/datum/station_trait/trait in SSstation.station_traits) + var/decal_color = trait.get_decal_color(thing_to_color, pattern) + if(decal_color) + return decal_color + return null + +///Return a color for the decals, if any +/datum/station_trait/proc/get_decal_color(thing_to_color, pattern) + return diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 4fc6a464a9c..922b05e2464 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -267,7 +267,7 @@ name = "Ionic Stormfront" report_message = "An ionic stormfront is passing over your station's system. Expect an increased likelihood of ion storms afflicting your station's silicon units." trait_type = STATION_TRAIT_NEGATIVE - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 3 event_control_path = /datum/round_event_control/ion_storm weight_multiplier = 2 @@ -276,7 +276,7 @@ name = "Radiation Stormfront" report_message = "A radioactive stormfront is passing through your station's system. Expect an increased likelihood of radiation storms passing over your station, as well the potential for multiple radiation storms to occur during your shift." trait_type = STATION_TRAIT_NEGATIVE - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 2 event_control_path = /datum/round_event_control/radiation_storm weight_multiplier = 1.5 @@ -286,7 +286,7 @@ name = "Dust Stormfront" report_message = "The space around your station is clouded by heavy pockets of space dust. Expect an increased likelyhood of space dust storms damaging the station hull." trait_type = STATION_TRAIT_NEGATIVE - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 2 event_control_path = /datum/round_event_control/meteor_wave/dust_storm weight_multiplier = 2 @@ -410,3 +410,255 @@ continue CHECK_TICK + +///Station traits that influence the space background and apply some unique effects! +/datum/station_trait/nebula + name = "Nebula" + trait_flags = STATION_TRAIT_ABSTRACT + weight = 0 + + show_in_report = TRUE + + ///The color of the "nebula" we send to the players client + var/nebula_color + ///The parallax layer of the nebula + var/nebula_layer = /atom/movable/screen/parallax_layer/random/space_gas + ///The color space 'glows' + var/space_light_color = COLOR_STARLIGHT + ///If set, gives the basic carp different colors + var/carp_color_override + +/datum/station_trait/nebula/New() + . = ..() + + ///We set the parallax layer to give a visual effect + SSparallax.random_layer = nebula_layer + SSparallax.random_parallax_color = nebula_color //give a unique color to tell the player somethings up + GLOB.starlight_color = space_light_color //color starlight in our nebula color + + //parallax is generated for quick-joiners before we can change it, so reset it for them :/ + for(var/client/client as anything in GLOB.clients) + client.parallax_layers_cached?.Cut() + client.mob?.hud_used?.update_parallax_pref(client.mob) + + //Color the carp in unique colors to better blend with the nebula + if(carp_color_override) + GLOB.carp_colors = carp_color_override + +///Station nebula that incur some sort of effect if no shielding is created +/datum/station_trait/nebula/hostile + trait_flags = STATION_TRAIT_ABSTRACT + trait_processes = TRUE + + ///Intensity of the nebula + VAR_PRIVATE/nebula_intensity = -1 + ///The max intensity of a nebula + VAR_PROTECTED/maximum_nebula_intensity = 2 HOURS + ///How long it takes to go to the next nebula level/intensity + VAR_PROTECTED/intensity_increment_time = INFINITE + ///Objects that we use to calculate the current shielding level + var/list/shielding = list() + +/datum/station_trait/nebula/hostile/process(seconds_per_tick) + calculate_nebula_strength() + + apply_nebula_effect(nebula_intensity - get_shielding_level()) + +/datum/station_trait/nebula/hostile/on_round_start() + . = ..() + + addtimer(CALLBACK(src, PROC_REF(send_instructions)), 30 SECONDS) + +///Announce to the station what's going on and what they need to do +/datum/station_trait/nebula/hostile/proc/send_instructions() + return + +///Calculate how strong we currently are +/datum/station_trait/nebula/hostile/proc/calculate_nebula_strength() + nebula_intensity = min(STATION_TIME_PASSED() / intensity_increment_time, maximum_nebula_intensity) + +///Check how strong the stations shielding is +/datum/station_trait/nebula/hostile/proc/get_shielding_level() + var/shield_strength = 0 + for(var/atom/movable/shielder as anything in shielding) + if(!is_station_level(shielder.z)) + continue + var/datum/callback/callback = shielding[shielder] + shield_strength += callback.Invoke() + + return shield_strength + +///Add a shielding unit to ask for shielding +/datum/station_trait/nebula/hostile/proc/add_shielder(atom/movable/shielder, shielding_proc) + shielding[shielder] = CALLBACK(shielder, shielding_proc) + + RegisterSignal(shielder, COMSIG_QDELETING, PROC_REF(remove_shielder)) + +///Remove a shielding unit from our tracking +/datum/station_trait/nebula/hostile/proc/remove_shielder(atom/movable/shielder) + SIGNAL_HANDLER + + shielding.Remove(shielder) + +///The station did not set up shielding, start creating effects +/datum/station_trait/nebula/hostile/proc/apply_nebula_effect(effect_strength = 0) + return + +/proc/add_to_nebula_shielding(atom/movable/shielder, nebula_type, shielding_proc) + var/datum/station_trait/nebula/hostile/nebula = locate(nebula_type) in SSstation.station_traits + if(!nebula) + return FALSE + + nebula.add_shielder(shielder, shielding_proc) + +/// Name of the glow we use for the radioation effect outside +#define GLOW_NEBULA "glow_nebula" + +///The station will be inside a radioactive nebula! Space is radioactive and the station needs to start setting up nebula shielding +/datum/station_trait/nebula/hostile/radiation + name = "Radioactive Nebula" + trait_type = STATION_TRAIT_NEGATIVE + trait_flags = STATION_TRAIT_SPACE_BOUND //maybe when we can LOOK UP + weight = 1 + show_in_report = TRUE + report_message = "This station is located inside a radioactive nebula. Setting up nebula shielding is top-priority." + trait_to_give = STATION_TRAIT_RADIOACTIVE_NEBULA + + blacklist = list(/datum/station_trait/random_event_weight_modifier/rad_storms) + threat_reduction = 30 + dynamic_threat_id = "Radioactive Nebula" + + intensity_increment_time = 5 MINUTES + maximum_nebula_intensity = 1 HOURS + 40 MINUTES + + nebula_color = list(0,0,0,0, 0,2,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //very vibrant green + space_light_color = COLOR_VIBRANT_LIME + carp_color_override = list( + COLOR_CARP_GREEN = 1, + COLOR_CARP_TEAL = 1, + COLOR_CARP_PALE_GREEN = 1, + COLOR_CARP_DARK_GREEN = 1, + ) + ///When are we going to send them a care package? + COOLDOWN_DECLARE(send_care_package_at) + ///How long does the storm have to last for us to send a care package? + VAR_PROTECTED/send_care_package_time = 5 MINUTES + ///The glow of 'fake' radioactive objects in space + var/nebula_radglow = "#66ff33" + +/datum/station_trait/nebula/hostile/radiation/New() + . = ..() + + for(var/area/target as anything in get_areas(/area/space)) + RegisterSignals(target, list(COMSIG_AREA_ENTERED, COMSIG_AREA_INITIALIZED_IN), PROC_REF(on_entered)) + RegisterSignal(target, COMSIG_AREA_EXITED, PROC_REF(on_exited)) + +/datum/station_trait/nebula/hostile/radiation/on_round_start() + . = ..() + + //Let people order more nebula shielding + var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/engineering/rad_nebula_shielding_kit] + pack.special_enabled = TRUE + + //Give robotics some radiation protection modules for modsuits + var/datum/supply_pack/supply_pack_modsuits = new /datum/supply_pack/engineering/rad_protection_modules() + send_supply_pod_to_area(supply_pack_modsuits.generate(null), /area/station/science/robotics, /obj/structure/closet/supplypod/centcompod) + + //Send a nebula shielding unit to engineering + var/datum/supply_pack/supply_pack_shielding = new /datum/supply_pack/engineering/rad_nebula_shielding_kit() + if(!send_supply_pod_to_area(supply_pack_shielding.generate(null), /area/station/engineering/main, /obj/structure/closet/supplypod/centcompod)) + //if engineering isnt valid, just send it to the bridge + send_supply_pod_to_area(supply_pack_shielding.generate(null), /area/station/command/bridge, /obj/structure/closet/supplypod/centcompod) + + //Disables radstorms, they don't really make sense since we already have the nebula causing storms + var/datum/round_event_control/modified_event = locate(/datum/round_event_control/radiation_storm) in SSevents.control + modified_event.weight = 0 + +///They entered space? START BOMBING WITH RADS HAHAHAHA +/datum/station_trait/nebula/hostile/radiation/proc/on_entered(area/space, atom/movable/enterer) + SIGNAL_HANDLER + + if(!ismovable(enterer)) + return + + enterer.AddElement(/datum/element/radioactive, range = 0, minimum_exposure_time = NEBULA_RADIATION_MINIMUM_EXPOSURE_TIME) + //Don't actually make EVERY. SINGLE. THING. radioactive, just make them glow so people arent killed instantly + if(!SSradiation.can_irradiate_basic(enterer)) + //outline clashes too much with other outlines and creates pretty ugly lines + enterer.add_filter(GLOW_NEBULA, 2, list("type" = "drop_shadow", "color" = nebula_radglow, "size" = 2)) + +///Called when an atom leaves space, so we can remove the radiation effect +/datum/station_trait/nebula/hostile/radiation/proc/on_exited(area/space, atom/movable/exiter) + SIGNAL_HANDLER + + exiter.RemoveElement(/datum/element/radioactive, range = 0, minimum_exposure_time = NEBULA_RADIATION_MINIMUM_EXPOSURE_TIME) + exiter.remove_filter(GLOW_NEBULA) + +/datum/station_trait/nebula/hostile/radiation/apply_nebula_effect(effect_strength = 0) + //big bombad now + if(effect_strength > 0) + if(!SSweather.get_weather_by_type(/datum/weather/rad_storm/nebula)) + COOLDOWN_START(src, send_care_package_at, send_care_package_time) + SSweather.run_weather(/datum/weather/rad_storm/nebula) + + //Send a care package to temporarily lift the storm! + if(COOLDOWN_FINISHED(src, send_care_package_at)) + COOLDOWN_START(src, send_care_package_at, send_care_package_time) + var/obj/machinery/nebula_shielding/emergency/rad_shield = /obj/machinery/nebula_shielding/emergency/radiation + + priority_announce( + {"Is everything okay there? We're getting high radiation readings from inside the station. \ + We're sending an emergency shielding unit for now, it will last [initial(rad_shield.detonate_in) / (1 MINUTES)] minutes. \n\n\ + Set up the nebula shielding. You can order construction kits at cargo if yours have been lost. + "} + ) + + addtimer(CALLBACK(src, PROC_REF(send_care_package)), 10 SECONDS) + return + + //No storms, shielding is good! + var/datum/weather/weather = SSweather.get_weather_by_type(/datum/weather/rad_storm/nebula) + weather?.wind_down() + COOLDOWN_RESET(src, send_care_package_at) + +///Send a care package because it is not going well +/datum/station_trait/nebula/hostile/radiation/proc/send_care_package() + new /obj/effect/pod_landingzone (get_safe_random_station_turf(), new /obj/structure/closet/supplypod/centcompod (), new /obj/machinery/nebula_shielding/emergency/radiation ()) + +/datum/station_trait/nebula/hostile/radiation/send_instructions() + var/obj/machinery/nebula_shielding/shielder = /obj/machinery/nebula_shielding/radiation + var/obj/machinery/gravity_generator/main/innate_shielding = /obj/machinery/gravity_generator/main + //How long do we have untill the first shielding unit needs to be up? + var/deadline = "[(initial(innate_shielding.radioactive_nebula_shielding) * intensity_increment_time) / (1 MINUTES)] minute\s" + //For how long each shielding unit will protect for + var/shielder_time = "[(initial(shielder.shielding_strength) * intensity_increment_time) / (1 MINUTES)] minute\s" + //Max shielders, excluding the grav-gen to avoid confusion when that goes down + var/max_shielders = ((maximum_nebula_intensity / intensity_increment_time)) / initial(shielder.shielding_strength) + + var/announcement = {"Your station has been constructed inside a radioactive nebula. \ + Standard spacesuits will not protect against the nebula and using them is strongly discouraged. \n\n\ + + EXTREME IMPORTANCE: The station is falling deeper into the nebula, and the gravity generator's innate radiation shielding \ + will not hold very long. Your engineering department has been supplied with all the necessary supplies to set up \ + shields to protect against the nebula. Additional supply crates can be ordered at cargo. \n\n\ + You have [deadline] before the nebula enters the station. \ + Every shielding unit will provide an additional [shielder_time] of protection, fully protecting the station with [max_shielders] shielding units. + "} + + priority_announce(announcement, sound = 'sound/misc/notice1.ogg') + + //Set the display screens to the radiation alert + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + if(!frequency) + return + + var/datum/signal/signal = new + signal.data["command"] = "alert" + signal.data["picture_state"] = "radiation" + + var/atom/movable/virtualspeaker/virtual_speaker = new(null) + frequency.post_signal(virtual_speaker, signal) + +/datum/station_trait/nebula/hostile/radiation/get_decal_color(atom/thing_to_color, pattern) + if(istype(get_area(thing_to_color), /area/station/hallway)) //color hallways green + return COLOR_GREEN diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index 0fe10d9bce0..e63fcf399ad 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -161,49 +161,49 @@ /datum/station_trait/deathrattle_department/service name = "Deathrattled Service" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_SERVICE department_name = "Service" /datum/station_trait/deathrattle_department/cargo name = "Deathrattled Cargo" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_CARGO department_name = "Cargo" /datum/station_trait/deathrattle_department/engineering name = "Deathrattled Engineering" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_ENGINEERING department_name = "Engineering" /datum/station_trait/deathrattle_department/command name = "Deathrattled Command" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_COMMAND department_name = "Command" /datum/station_trait/deathrattle_department/science name = "Deathrattled Science" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_SCIENCE department_name = "Science" /datum/station_trait/deathrattle_department/security name = "Deathrattled Security" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_SECURITY department_name = "Security" /datum/station_trait/deathrattle_department/medical name = "Deathrattled Medical" - trait_flags = NONE + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED weight = 1 department_to_apply_to = DEPARTMENT_BITFLAG_MEDICAL department_name = "Medical" diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm index 5e736277f8f..e9cadf5687b 100644 --- a/code/datums/weather/weather_types/radiation_storm.dm +++ b/code/datums/weather/weather_types/radiation_storm.dm @@ -75,5 +75,38 @@ else signal.data["command"] = "shuttle" - var/atom/movable/virtualspeaker/virt = new(null) - frequency.post_signal(virt, signal) + var/atom/movable/virtualspeaker/virtual_speaker = new(null) + frequency.post_signal(virtual_speaker, signal) + +/// Used by the radioactive nebula when the station doesnt have enough shielding +/datum/weather/rad_storm/nebula + protected_areas = list(/area/shuttle) + + weather_overlay = "nebula_radstorm" + weather_duration_lower = 100 HOURS + weather_duration_upper = 100 HOURS + + end_message = null + + ///Chance we pulse a living during the storm + var/radiation_chance = 20 + +/datum/weather/rad_storm/nebula/weather_act(mob/living/living) + if(!prob(radiation_chance)) + return + + if(!SSradiation.can_irradiate_basic(living) || SSradiation.wearing_rad_protected_clothing(living)) + return + + radiation_pulse( + source = living, + max_range = 0, + threshold = RAD_LIGHT_INSULATION, + chance = URANIUM_IRRADIATION_CHANCE, + minimum_exposure_time = NEBULA_RADIATION_MINIMUM_EXPOSURE_TIME, + ) + +/datum/weather/rad_storm/nebula/status_alarm(active) + if(!active) //we stay on + return + ..() diff --git a/code/game/area/areas/misc.dm b/code/game/area/areas/misc.dm index f2c7ac62c15..c7273c75aac 100644 --- a/code/game/area/areas/misc.dm +++ b/code/game/area/areas/misc.dm @@ -23,9 +23,6 @@ /area/space/nearstation icon_state = "space_near" area_flags = UNIQUE_AREA | AREA_USES_STARLIGHT - static_lighting = TRUE - base_lighting_alpha = 0 - base_lighting_color = COLOR_WHITE /area/misc/start name = "start area" diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 863e24d454d..7e370257dbe 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -15,6 +15,8 @@ GLOBAL_VAR_INIT(dynamic_stacking_limit, 90) GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset) // Forced threat level, setting this to zero or higher forces the roundstart threat to the value. GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) +/// Modify the threat level for station traits before dynamic can be Initialized. List(instance = threat_reduction) +GLOBAL_LIST_EMPTY(dynamic_station_traits) /datum/game_mode/dynamic // Threat logging vars @@ -395,6 +397,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) var/relative_threat = LORENTZ_DISTRIBUTION(threat_curve_centre, threat_curve_width) threat_level = clamp(round(lorentz_to_amount(relative_threat), 0.1), 0, max_threat_level) + for(var/datum/station_trait/station_trait in GLOB.dynamic_station_traits) + threat_level = max(threat_level - GLOB.dynamic_station_traits[station_trait], 0) + log_dynamic("Threat reduced by [GLOB.dynamic_station_traits[station_trait]]. Source: [type].") + if (SSticker.totalPlayersReady < low_pop_player_threshold) threat_level = min(threat_level, LERP(low_pop_maximum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold)) @@ -442,6 +448,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) continue vars[variable] = configuration["Dynamic"][variable] + configure_station_trait_costs() setup_parameters() setup_hijacking() setup_shown_threat() @@ -777,6 +784,26 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(CONFIG_GET(flag/protect_assistant_from_antagonist)) ruleset.restricted_roles |= JOB_ASSISTANT +/// Get station traits and call for their config +/datum/game_mode/dynamic/proc/configure_station_trait_costs() + if(!CONFIG_GET(flag/dynamic_config_enabled)) + return + for(var/datum/station_trait/station_trait as anything in GLOB.dynamic_station_traits) + configure_station_trait(station_trait) + +/// Apply configuration for station trait costs +/datum/game_mode/dynamic/proc/configure_station_trait(datum/station_trait/station_trait) + var/list/station_trait_config = LAZYACCESSASSOC(configuration, "Station", station_trait.dynamic_threat_id) + var/cost = station_trait_config["cost"] + + if(isnull(cost)) //0 is valid so check for null specifically + return + + if(cost != GLOB.dynamic_station_traits[station_trait]) + log_dynamic("Config set [station_trait.dynamic_threat_id] cost from [station_trait.threat_reduction] to [cost]") + + GLOB.dynamic_station_traits[station_trait] = cost + /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) mid_round_budget = min(threat_level, mid_round_budget + regain) diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md index e584ac4ee4a..d663e88a239 100644 --- a/code/game/gamemodes/dynamic/readme.md +++ b/code/game/gamemodes/dynamic/readme.md @@ -130,6 +130,11 @@ The format of this file is: "Latejoin": { /* Same as "Roundstart", but for `/datum/dynamic_ruleset/latejoin` instead. */ + }, + + "Station": { + /* Special threat reductions for dangerous station traits. Traits are selected before dynamic, so traits will always */ + /* reduce threat even if there's no threat for it available. Only "cost" can be modified */ } } ``` diff --git a/code/game/machinery/nebula_shielding.dm b/code/game/machinery/nebula_shielding.dm new file mode 100644 index 00000000000..091f97bb947 --- /dev/null +++ b/code/game/machinery/nebula_shielding.dm @@ -0,0 +1,129 @@ +///Machinery that block nebulas. This type is for convenience, you can set nebula shielding on other objects as well using add_to_nebula_shielding() +/obj/machinery/nebula_shielding + density = TRUE + + icon = 'icons/obj/machines/nebula_shielding.dmi' + pixel_x = -16 + + layer = FLY_LAYER + plane = ABOVE_GAME_PLANE + + ///Strength of the shield we apply + var/shielding_strength + ///The type of nebula that we shield against + var/nebula_type + ///How much power we use every time we block the nebula's effects + var/power_use_per_block = BASE_MACHINE_ACTIVE_CONSUMPTION * 2 + ///State we use when actively blocking a nebula + var/active_icon_state + +/obj/machinery/nebula_shielding/Initialize(mapload) + . = ..() + + add_to_nebula_shielding(src, nebula_type, PROC_REF(get_nebula_shielding)) + +///Nebula is asking us how strong we are. Return our shield strength is all is well +/obj/machinery/nebula_shielding/proc/get_nebula_shielding() + if(panel_open) + return + if(!powered()) + icon_state = initial(icon_state) + return + + use_power_from_net(power_use_per_block) + generate_reward() + icon_state = active_icon_state + return shielding_strength + +///Generate a resource for defending against the nebula +/obj/machinery/nebula_shielding/proc/generate_reward() + return + +///Short-lived nebula shielding sent by centcom in-case there hasn't been shielding for a while +/obj/machinery/nebula_shielding/emergency + density = TRUE + anchored = FALSE //so some handsome rogue could potentially move it off the station z-level + shielding_strength = 999 //should block the nebula completely + + ///How long we work untill we self-destruct + var/detonate_in = 10 MINUTES + +/obj/machinery/nebula_shielding/emergency/Initialize(mapload) + . = ..() + + addtimer(CALLBACK(src, PROC_REF(self_destruct)), detonate_in) + +///We don't live for very long, so self-destruct +/obj/machinery/nebula_shielding/emergency/proc/self_destruct() + explosion(src, light_impact_range = 5, flame_range = 3, explosion_cause = src) + qdel(src) + +/obj/machinery/nebula_shielding/emergency/examine(mob/user) + . = ..() + + . += span_notice("[p_They()] will block the nebula for [round(detonate_in / (1 MINUTES))] minute\s with a shield strength of [shielding_strength].") + +/obj/machinery/nebula_shielding/emergency/get_nebula_shielding() + return shielding_strength //no strings attached, we will always produce shielding + +/obj/machinery/nebula_shielding/emergency/generate_reward() + return //no reward for you + +///We shield against the radioactive nebula and passively generate tritium +/obj/machinery/nebula_shielding/radiation + name = "radioactive nebula shielder" + desc = "Generates a field around the station, protecting it from a radioactive nebula." + + icon_state = "radioactive_shielding" + active_icon_state = "radioactive_shielding_on" + + circuit = /obj/item/circuitboard/machine/radioactive_nebula_shielding + + nebula_type = /datum/station_trait/nebula/hostile/radiation + shielding_strength = 4 + +/obj/machinery/nebula_shielding/radiation/examine(mob/user) + . = ..() + + . += span_notice("Passively generates tritium. Provides [shielding_strength] levels of nebula shielding when active.") + +/obj/machinery/nebula_shielding/radiation/generate_reward() + var/turf/open/turf = get_turf(src) + if(isopenturf(turf)) + turf.atmos_spawn_air("[GAS_TRITIUM]=1;[TURF_TEMPERATURE(T20C)]") + +/obj/machinery/nebula_shielding/radiation/attackby(obj/item/item, mob/user, params) + if(default_deconstruction_screwdriver(user, initial(icon_state) + "_open", initial(icon_state), item)) + return + + if(default_deconstruction_crowbar(item)) + return + + return ..() + +///Emergency shielding so people aren't permanently in a radstorm if shit goes very wrong in engineering +/obj/machinery/nebula_shielding/emergency/radiation + name = "emergency nebula radiation shielder" + desc = "Generates a field around the station to protect it from a radioactive nebula." + + icon = 'icons/obj/machines/engine/other.dmi' + icon_state = "portgen1_1" + pixel_x = 0 + + nebula_type = /datum/station_trait/nebula/hostile/radiation + +/obj/machinery/nebula_shielding/emergency/radiation/self_destruct() + var/turf/open/turf = get_turf(src) + if(isopenturf(turf)) + turf.atmos_spawn_air("[GAS_TRITIUM]=50;[TURF_TEMPERATURE(T20C)]") //causes a small tritium fire when combined with the explosion + + ..() + +///Small explanation for engineering on how to set-up the radioactive nebula shielding +/obj/item/paper/fluff/radiation_nebula + name = "radioactive nebula shielding" + default_raw_text = {"EXTREME IMPORTANCE!!!!
+ Set up these radioactive nebula shielding units before the gravity generators native shielding is overwhelmed!
+ Shielding units passively generate tritium, so make sure to properly ventilate/isolate the area before setting up a shielding unit! + More circuit boards can be ordered through cargo. Consider setting up auxillary shielding units in-case of destruction, power loss or sabotage. + "} diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 1c74766ac69..2c6d01b5bc4 100644 --- a/code/game/objects/effects/decals/decal.dm +++ b/code/game/objects/effects/decals/decal.dm @@ -64,9 +64,10 @@ // If the tile uses holiday colors, apply them here if(use_holiday_colors) - var/current_holiday_color = request_holiday_colors(src, pattern) - if(current_holiday_color) - color = current_holiday_color + + var/custom_color = request_station_colors(src, pattern) || request_holiday_colors(src, pattern) + if(custom_color) + color = custom_color alpha = DECAL_ALPHA var/turf/T = loc diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 825e8bd9d9d..8af2c2bbfc6 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1499,3 +1499,13 @@ greyscale_colors = CIRCUIT_COLOR_SCIENCE build_path = /obj/machinery/navbeacon req_components = list() + +/obj/item/circuitboard/machine/radioactive_nebula_shielding + name = "Radioactive Nebula Shielding" + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/nebula_shielding/radiation + req_components = list( + /datum/stock_part/capacitor = 2, + /obj/item/mod/module/rad_protection = 1, + /obj/item/stack/sheet/plasteel = 2, + ) diff --git a/code/game/objects/items/storage/boxes/job_boxes.dm b/code/game/objects/items/storage/boxes/job_boxes.dm index abf63629000..118229146eb 100644 --- a/code/game/objects/items/storage/boxes/job_boxes.dm +++ b/code/game/objects/items/storage/boxes/job_boxes.dm @@ -42,6 +42,9 @@ new /obj/item/flashlight/flare(src) new /obj/item/radio/off(src) + if(HAS_TRAIT(SSstation, STATION_TRAIT_RADIOACTIVE_NEBULA)) + new /obj/item/storage/pill_bottle/potassiodide(src) + /obj/item/storage/box/survival/radio/PopulateContents() ..() // we want the survival stuff too. new /obj/item/radio/off(src) diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm index 57135bf0bda..2804ebfe00d 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -21,6 +21,10 @@ anchored = TRUE /obj/structure/closet/emcloset/Initialize(mapload) + if(HAS_TRAIT(SSstation, STATION_TRAIT_RADIOACTIVE_NEBULA) && prob(30)) + new /obj/structure/closet/radiation(loc) + return INITIALIZE_HINT_QDEL + . = ..() if (prob(1)) diff --git a/code/game/turfs/open/floor/glass.dm b/code/game/turfs/open/floor/glass.dm index beb1535e694..af2a0bec5bf 100644 --- a/code/game/turfs/open/floor/glass.dm +++ b/code/game/turfs/open/floor/glass.dm @@ -21,8 +21,8 @@ var/list/glow_stuff /// How much alpha to leave when cutting away emissive blockers var/alpha_to_leave = 255 - /// Color of starlight to use - var/starlight_color = COLOR_STARLIGHT + /// Color of starlight to use. Defaults to STARLIGHT_COLOR if not set + var/starlight_color /turf/open/floor/glass/broken_states() return list("glass-damaged1", "glass-damaged2", "glass-damaged3") @@ -51,7 +51,7 @@ return glow_stuff = partially_block_emissives(src, alpha_to_leave) - set_light(2, 0.75, starlight_color) + set_light(2, 0.75, starlight_color || GLOB.starlight_color) /turf/open/floor/glass/make_plating() return diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 024dcd80a89..04eedb6619f 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -1,3 +1,6 @@ +///The color of light space emits +GLOBAL_VAR_INIT(starlight_color, COLOR_STARLIGHT) + /turf/open/space icon = 'icons/turf/space.dmi' icon_state = "space" @@ -21,7 +24,6 @@ plane = PLANE_SPACE layer = SPACE_LAYER light_power = 0.75 - light_color = COLOR_STARLIGHT space_lit = TRUE bullet_bounce_sound = null vis_flags = VIS_INHERIT_ID //when this be added to vis_contents of something it be associated with something on clicking, important for visualisation of turf in openspace and interraction with openspace that show you turf. @@ -54,6 +56,7 @@ stack_trace("Warning: [src]([type]) initialized multiple times!") flags_1 |= INITIALIZED_1 + light_color = GLOB.starlight_color // We make the assumption that the space plane will never be blacklisted, as an optimization if(SSmapping.max_plane_offset) diff --git a/code/modules/cargo/packs/_packs.dm b/code/modules/cargo/packs/_packs.dm index 479d2595c02..357f32f3cb4 100644 --- a/code/modules/cargo/packs/_packs.dm +++ b/code/modules/cargo/packs/_packs.dm @@ -79,6 +79,21 @@ /datum/supply_pack/proc/generate_supply_packs() return +///Easily send a supplypod to an area +/proc/send_supply_pod_to_area(contents, area_type, pod_type = /obj/structure/closet/supplypod) + var/list/areas = get_areas(area_type) + if(!LAZYLEN(areas)) + return FALSE + var/list/open_turfs = list() + for(var/turf/open/floor/found_turf in get_area_turfs(pick(areas), subtypes = TRUE)) + open_turfs += found_turf + + if(!length(open_turfs)) + return FALSE + + new /obj/effect/pod_landingzone (pick(open_turfs), new pod_type (), contents) + return TRUE + /** * Custom supply pack * The contents are given on New rather than being static diff --git a/code/modules/cargo/packs/engineering.dm b/code/modules/cargo/packs/engineering.dm index 1c3ec6c813a..ef6e0a9e726 100644 --- a/code/modules/cargo/packs/engineering.dm +++ b/code/modules/cargo/packs/engineering.dm @@ -292,3 +292,25 @@ crate_name = "HFR crate" crate_type = /obj/structure/closet/crate/secure/engineering dangerous = TRUE + +/datum/supply_pack/engineering/rad_protection_modules + name = "Radiation Protection Modules" + desc = "Contains multiple radiation protections modules for MODsuits." + hidden = TRUE + contains = list(/obj/item/mod/module/rad_protection = 3) + crate_name = "modsuit radiation modules" + crate_type = /obj/structure/closet/crate/engineering + +/datum/supply_pack/engineering/rad_nebula_shielding_kit + name = "Radioactive Nebula Shielding" + desc = "Contains circuitboards and radiation modules for constructing radioactive nebula shielding." + cost = CARGO_CRATE_VALUE * 2 + + special = TRUE + contains = list( + /obj/item/mod/module/rad_protection = 5, + /obj/item/circuitboard/machine/radioactive_nebula_shielding = 5, + /obj/item/paper/fluff/radiation_nebula = 1, + ) + crate_name = "radioactive nebula shielding (IMPORTANT)" + crate_type = /obj/structure/closet/crate/engineering diff --git a/code/modules/cargo/packs/materials.dm b/code/modules/cargo/packs/materials.dm index a6fdd73e77f..68dacd730be 100644 --- a/code/modules/cargo/packs/materials.dm +++ b/code/modules/cargo/packs/materials.dm @@ -82,14 +82,6 @@ crate_name = "fuel tank crate" crate_type = /obj/structure/closet/crate/large -/datum/supply_pack/materials/hightank - name = "Large Water Tank Crate" - desc = "Contains a high-capacity water tank. Useful for botany or other service jobs." - cost = CARGO_CRATE_VALUE * 2.4 - contains = list(/obj/structure/reagent_dispensers/watertank/high) - crate_name = "high-capacity water tank crate" - crate_type = /obj/structure/closet/crate/large - /datum/supply_pack/materials/hightankfuel name = "Large Fuel Tank Crate" desc = "Contains a high-capacity fuel tank. Keep contents away from open flame." diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm index c11130dcbc1..f7e1ca2d868 100644 --- a/code/modules/lighting/lighting_area.dm +++ b/code/modules/lighting/lighting_area.dm @@ -64,7 +64,7 @@ lighting_effect.layer = LIGHTING_PRIMARY_LAYER lighting_effect.blend_mode = BLEND_ADD lighting_effect.alpha = base_lighting_alpha - lighting_effect.color = base_lighting_color + lighting_effect.color = (base_lighting_color == COLOR_STARLIGHT ? GLOB.starlight_color : base_lighting_color) lighting_effect.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR lighting_effects += lighting_effect add_overlay(lighting_effects[1]) diff --git a/code/modules/lighting/static_lighting_area.dm b/code/modules/lighting/static_lighting_area.dm index dc8e9c48879..7dc6cc6c3d8 100644 --- a/code/modules/lighting/static_lighting_area.dm +++ b/code/modules/lighting/static_lighting_area.dm @@ -7,7 +7,7 @@ GLOBAL_LIST_INIT_TYPED(fullbright_overlays, /mutable_appearance, list(create_ful SET_PLANE_W_SCALAR(lighting_effect, LIGHTING_PLANE, offset) lighting_effect.layer = LIGHTING_PRIMARY_LAYER lighting_effect.blend_mode = BLEND_ADD - lighting_effect.color = COLOR_STARLIGHT + lighting_effect.color = GLOB.starlight_color return lighting_effect /area diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index f57b7b4f20d..6ce40d8c1a3 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -78,25 +78,6 @@ /obj/machinery/vending, /obj/structure/window, )) - /// Weighted list of colours a carp can be - /// Weighted list of usual carp colors - var/static/list/carp_colors = list( - COLOR_CARP_PURPLE = 7, - COLOR_CARP_PINK = 7, - COLOR_CARP_GREEN = 7, - COLOR_CARP_GRAPE = 7, - COLOR_CARP_SWAMP = 7, - COLOR_CARP_TURQUOISE = 7, - COLOR_CARP_BROWN = 7, - COLOR_CARP_TEAL = 7, - COLOR_CARP_LIGHT_BLUE = 7, - COLOR_CARP_RUSTY = 7, - COLOR_CARP_RED = 7, - COLOR_CARP_YELLOW = 7, - COLOR_CARP_BLUE = 7, - COLOR_CARP_PALE_GREEN = 7, - COLOR_CARP_SILVER = 1, // The rare silver carp - ) /mob/living/basic/carp/Initialize(mapload, mob/tamer) ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) //Need to set before init cause if we init in hyperspace we get dragged before the trait can be added @@ -138,7 +119,7 @@ /mob/living/basic/carp/proc/apply_colour() if (!greyscale_config) return - set_greyscale(colors = list(pick_weight(carp_colors))) + set_greyscale(colors = list(pick_weight(GLOB.carp_colors))) /// Called when another mob has forged a bond of friendship with this one, passed the taming mob as 'tamer' /mob/living/basic/carp/proc/on_tamed(mob/tamer, feedback = TRUE) diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index c2621aac9ce..4d32559ce71 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -148,6 +148,9 @@ GLOBAL_LIST_EMPTY(gravity_generators) /// Audio for when the gravgen is on var/datum/looping_sound/gravgen/soundloop + ///Amount of shielding we offer against a radioactive nebula + var/radioactive_nebula_shielding = 4 + ///Station generator that spawns with gravity turned off. /obj/machinery/gravity_generator/main/off on = FALSE @@ -162,6 +165,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) enable() center_part.add_overlay("activated") + add_to_nebula_shielding(src, /datum/station_trait/nebula/hostile/radiation, PROC_REF(get_radioactive_nebula_shielding)) + /obj/machinery/gravity_generator/main/Destroy() // If we somehow get deleted, remove all of our other parts. investigate_log("was destroyed!", INVESTIGATE_GRAVITY) disable() @@ -467,6 +472,10 @@ GLOBAL_LIST_EMPTY(gravity_generators) for(var/obj/machinery/gravity_generator/part as anything in generator_parts) SET_PLANE(part, PLANE_TO_TRUE(part.plane), new_turf) +/// Returns the radioactive shielding (if there's a radioactive nebula). Called from a callback set in add_to_nebula_shielding() +/obj/machinery/gravity_generator/main/proc/get_radioactive_nebula_shielding() + return on ? radioactive_nebula_shielding : 0 + //prevents shuttles attempting to rotate this since it messes up sprites /obj/machinery/gravity_generator/main/shuttleRotate(rotation, params) params = NONE diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index f958414c1d3..88e205e8240 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -1668,6 +1668,12 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) +/datum/techweb_node/mod_advanced_engineering/New() + if(HAS_TRAIT(SSstation, STATION_TRAIT_RADIOACTIVE_NEBULA)) //we'll really need the rad protection modsuit module + starting_node = TRUE + + return ..() + /datum/techweb_node/mod_medical id = "mod_medical" display_name = "Medical Modular Suits" diff --git a/config/dynamic.json b/config/dynamic.json index 53f2eaa6560..a4a1eb7ebdb 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -154,5 +154,10 @@ "Stowaway Changeling": { "weight": 0 } + }, + + "Station": { + "Radioactive Nebula": { + } } } diff --git a/icons/effects/weather_effects.dmi b/icons/effects/weather_effects.dmi index 00083c464a24f170a794728287929a77d2b85471..fa21b2d12eb63cb188b689d61f19313a32cdf1ce 100644 GIT binary patch delta 6766 zcmXX}c_7pO|KD5@IYLtsP35Lfm@{(BQErn6lOuAgA;-KGmB^L*jJc1TITAyq93i)n zjL2-RoU@sJ)AzSO-|yG^c|4wv=i~8O_Y}Bi3Y>Te_#T3|gVJ?<QCj6})5H7cZeUQBR!5&<%tTzpGn7%cKDB4jjf+-SS&qW!L97^^ozidG2jC&T2$ILW5D58l<3H~(r?&lWx1c(mRgs?m8hAZR%U zH+!J6J<#k1Y#;Os01y&ushm7uIQ;S2t?-Yd=Y1;bO!i0ny)xBWne*8|3Sg!zQ24>> zG`OYg7pm{Zg2#>3#pt=`G!t{cPd79OY7-NYn{6X}uyHm`AT*zAYUFFwF0#z`#Dy$y zyw6vuxlApj@aEX_@oyG2_F#u|bI%hFXQbELg~)Ht!axp4oBm7(8z&2(Fv!crSL%8L z&(wKF1@Lz!ZV!fx_+6-@xtk@;7?P_laGG}StJ|0Wy#91u*WSqKS_y{xKJ|egycu~r zfpzW((HVcDnmqZ4HCbCmrr}3ZqrjaOHuXJ?y|iJPm~C0NS7u9bg3MW!g=qg!H5i!7 zp80}3&py}_s~Pzj(J_6;WjjpWkQ1*W7nP7mj)B^^?{J3Z5%?EmaSy^;6kTwe&l zhcJZgM|yofNZ^@xVykp#*}-t?O3$E{9zpQr%Ta8Ty{)C~FyCP-q1lZwVl1*Df zQ^?hq5fIIHLN({z-c%g?6!P-3x#M)}<5JX`#@tUXj>(9>Z9R*~ny`$^lSix9u8yTa zy`wYiJVcyoX2SPbBScr3>4S4;U{wUaGLE8(m&P@Ji9RCuP()SHkk?DD_;vrh)RSIg z6?LZ6oo)~S#d~Hy>5nzCR++niI9*#j|0Sc{ijPq_1R1IyDz}-S==kS1u@vdS<2Q75 zld$2#2*vxBxcwnhpKI}gj6nrgB09vQBSK%MZ7!8sLU*B21_7Dyoym@v#ijnvbS3Z$ zmOG^s+<8GhhY&}>pVUL?KyMeGuh$}iINo)HWY+>p=^*?ebmqo;Pxj4E?44ZkAmxem zD#a@YVDjKzApXhO^X|0}RgJ)8TnSsml?ZO$PTEkJekXS8OB#4s`2La({BT%bFDZ;c z+V-)(8p&v~y20p90`2G32-XhTp){v2qsDmnvPO(RM^i>EST^QKsZv98Tlg<2?|w80 z`yLP~HT{OSymcd`whLc(&Tl`@o7}%KuwV_Vm$vzTK9V0xLZbS$N-SBfMi98NVa90P zUi}xZ^-Tuv1!q2x_N}fpLF;SAid@JAao4rgm~*TibzU_Q_M8v-$WoXZ6|gSmjAy5H zu4@Xg92GH#p#|u0&dcBG{Loz61^AFy(pr|@%xS(F6|-$g0{yjMbZ5wC6B%Rj!@Dns z^Ax7LvmwJ(A7J`F$(*&_sw-IR>=>GdtTn%C`Ft%tB8anAd;t%c(F{*Mm1DZw@FlDP zy_HI;inPDuvamM`D7>0{-Pk5Uf z7L|PMoRi>86Fff~dC2~;Xudz=&JD^6pqzg^ke zee&j870k;qp!3J{kc);X7lWD-ShKZ7Bi4q&@sS#JLR5-Q$bW0CL0%dx7SYE1!>*4U zt%=SpzWGWtt;$8`+cgXOwn5b{UcniO3o~&5?XPWB{#{C`{4U8kZXt6 zsAFW`SVA0Z%r=*pp8U7yd=QkDP z0t^2!P(S)`{Af(2QGYP>YhLa6=y0VaNRax2%Y)}O=+#e_R~&Eif_$E&et0Bb8!ENg zbDA1d&(YT2b0xp>mY0a8;#ZbvMu1ra{Bv8nHk3;+>(5FV2&Vd#LI+gA`4f;pwj4n4H z&<7>3D5DqjafoPyc-PnT2@)bW~_gT79wxJ_0 zckHu`?xeQv86Ub@gDL_8(9_X(tm2p7IvTB(n*X+42z~Kn--zL_*<8G^y%SP`yrfRu zTGjOS>agFjs{P5Hwy-hgimx63y1&2=LcuWdng?+2fBZB7*<^6a1}~@ds#IFOsiHw0 zth!%R+w;gIy3O#x_Z57h<`QyGdrWYoSLxQEOPzl&yy;4g?EJ;pX~CiXo~QF9DaQb$ zj$)Hy+a$Bxu02yHM6fNP_0$k13NF_Pd1F#Kf$k2r$p$PjIa}}_d#HJtFJR1F!?)!5hlRVBQyVub&pQVGeF+@(G@I_c5TrN{fb5EA^+zc$ zy?AKm)opJdmLwMs2I~TR)Qglt0gIDRL6hZYlh|}$x>4``nhDQ*s4*=tEX*_`9@1oS z@}q8oyK_k&gD9%ba^#ow$HY0(Y^Y%-J56v0`uF+OP-mm(wW_kl#OYpBL;;aCa zxP4r?2~b8EmZ(`sCU9NP)&)}JEid+{@wn9{dGYhYy2KKiv(q~c?%5MXaO^>>w zu@%Nczd2$PJRcoL(3!)=)8lQeBq)EDPSKBuJ(_s()-^jGNKl_s<pMUo`MMNo1&olL6PhMF`pOjNS&-Kw!{tXHvuCdoE>b>e(k@CuY|#a_>8t)2 z&7J<t%_w=w5REK}2ZpRb8JToMwa z=yc?)X82M*-IZmXymc9)xeDc@Y5AR?)4RayIxwl*S&}Us9B7n&Qy0jn(h=c;F-XPoX<8D{mc7Cy)RM1RQd(L__+Fqx zC387DI}enXm(vdC;`9oGj|R4t!xzZmIF&?!QuM*njBdRmJ9*Yi~2F z3qyUUe~4MGe)yA|_vZ>|X-}k8sTDS|Ym;$T`9y7#M8n28>fX9q*=W4gMst-{>p*X{ z^~6R1E#`}WV?d6OE>(=(Z5L{xjF%$65R-n7(+R7&9H`zBz{W zS)r=aW2&gDdnx?aZ`O1@Rp<_4y~ZnF{qOH!kmp9_H-}7(@{_quSwU}NfG_9AsjA1v z>mTVpetUSdxEpHuxveYq>VN;$Ap;A)&^r&dN&PY)2laGza&iJPPqbfc_KVk-x<<9h z_}O_3Hcl_!In-s@;9l;?DcOzYow=3eORctlI4_ zPLb;{gnUvQy-I%h>o&FF$m&YdyE#<05KiT3S3#E$JitP$soHhMvh@dn;4U#I*)%GE zQ-KQYoQuheMic*|qwuXMcc(GBTFQ5|{=Yk0p^0nz4+wj2rG4PZiT4D6 z@nZ1>rf*Pw!TqUA7Upn*!Q~Bynf#7=d@}V}EF>@*YMZ;40jFsE8RyTRh#%Q0Ahh%o zR7hVZVmN9|70ahTG_qOqNwmZxbj3q;jG+8BMR+L-Lmgelsdxlxqi)Thn}z-q7o#Os zZ&>qr%S-88TnVuqk2XhL=jG*n4@}xDilX0?l$7j28Vh#<_}*R_X>yTjf)>?ip=6Gk zD(^PPO4WCm-42&J`5ZwS5G#Av-nN|74^G#TgG);8qRy7PkQO8@{=1UMulERiq zECK;#PEU&A2Cl7YDKvm3a^d62-u#zZHVql%4WxKly==aT~0PbrYv37(Up`PgVd{1ERfN}HA{5T z_1|~V1q}}$6=20lcP9gf^KKgK!V8DppWjBZiaPR|=qOoS+=VNSLttjix$ZR;aw9OE zGC4%BW0G(-k^LT%*T)A%QeA%sV-W`EE;Gg5XCAnBaSoF&0SW@ybDIM@26>QOzinS3 zN6w2`Jx3QX%z@OlFK$sG#;awzd%f78Sdz#-+PYLA7=Xc!LkW^1`N7j252o}th52T?o1Ju;IIq#${y{RmWV;PEdB49$S(7Va=939(rS5>8FKTQ zg^-_(i73fH%;HZu_WQF0gYIU^(@93wLaaf$=x(>|&Pd+#lVO!|D3a@4dX)wNovtdU zk`1~`DHzu3aR|oX*%+hReoLp)F5QeDNKz0bNyE_25OZ^apKP{!HU<5J@w2j zW33*8bP^TmwYv0EZX;dx5%gj}us_`ugDSeM*K=>K&V1N#gz?01r~;93>(?TK?%+OI zwpafWi{Fsb@#mv6|5$G8cLzKpl3$2%MXRQBHcMsM#i~}VI+R^1T3)hZ=|$mC?TECZ zdglsn6NTEIX~!n?;wb^iTOQ+O2~&!NAtnnmeM@thOrtxEhw9k~&n~@2zLU-}pL?;Ca(aqaN6CS{@S|O(! zDp~(1)Cvt2_{|ttzI#ZB(;FgtH7{M67%Y%gnDjjb1h*|ZDf`r0R#_;&1rniy zzNi!JTa-l6KGif1X-8N!OK=1z58OqOu3hxlK2T>{KFq{9T?}8IwY`bI!!H0bmULBP@U8w!!#0eP8os@NoD48lRDYvh_iF#-Ew;$_Jj zNV!}^Db{QR{9)|nYs%9C5BmDhDMoH*Eywm~$juRBp)I$OguO@y3*_cp+|Q_foo0eA zU1#ug6=9gOX{{*1;&(ZUv3LM2kwVdi(C2cjwS>jq$~|$3l{%&eQ8;j z`SaCIcNY)q>~lNLsipNdtQtSt)BHoK<>z7(Wk^pg`=@(iDB0(JiyEmSf$K*+UW27Sr0fT*}u!J>N={F)kjS6#ttL& zN{vJirxb@1>@+UDzRDlqGVDL>3-?95zl|(9QASt9a6q-*vri>2r%MIleJF)}?y>#I-#z+1?uHzEl`|!#h;oOpd5W_pht8$2@@2KWkUY~xlz%uG23AuY<|fAO zC_b}5raY6`eb!^bdJKq_uHkFtgY!w8Zqj|PyNCWH6-(K3bq>_Ow;Y4OfV{>5-yNP4 zM(6!fdD3ZFH8Q#4H;A$H(VV55V6@;B?n}7upuUmRXZ^lKy-;MbT!M=?(deBkR{iyE zA!RAK!EMOILxtWjt0!z8h01C|VJ?nCY}DwB3?(Cwi{6U|7DzKbBYKfIrM@t4fTkn51uy>2d&QOrW9nKS}Ghx$5(1k02HfrgLb-15+e6v|;ou*T|Kd_!Y4uaJp zXQx%q2>(rV!FHU&v5s+xyrMjX-eb^u%{4UvL0Qr5{wk6E?w+o=lm*hr)KvdN;M&c> kyz93lqa#E(m8Fk_?QPsW=6`4CgXlj)xQTAHw&T|zL` z$TZe4WMukH@9*dL`tv?sk9+Pp&*ynQ&%Hx)bnoZrGOr=uhg;hR>beFz^!M-!^ziio z0775(WR9@K$#Oww3!2UcN`5PU;e!!3d{Wjc7*io)&fyX}*}H>c=e2fZTa!zT8BrdH z50&<{iGw=z)m*xuDmZBM5>WK);G*)63$hKy)u$0xA>G>PF@lTFI~qaJ2H#nft8m+< z2Pv7~newg8K)-a;Jzft#3(Yq@vCUk%vx=Abz99AT!P&pl1*RvtKvi#}sr|vp3x;vU z=~2D$B2Uhp%C-i+zOzmIQaLap_zol9Tq7Au=a}=-Ag@*j)^fUPfqV|mW4jjPZdEB~ z{stsYvBrQrJT#?~-UXBHFjb($!?W{*dV|)09-bG{ofyT;Q=eQ~3vw&mVE!AKn9A8%2 zFTN0oOfo3T&2{iua6BXRmHx_Q*Xn26sx!LMk4D}}t9gAK;?{e%u<({Obob)!I8OlP zCfztA*JJv8DsqU`%~TgCK;}BV-_&iSd$Oaww-Ewpa-v&8icTR$Un{*aQjF$j-CYFT zsNNUQ@dDiP#q{+&Z#KzvI>mkB0poi8{m->I6MXYv_{L`R#%5Xp1NHc}A1Q9(*U4Z1 z3%%_H9Qywyc7v{a|Ct)++zYHAr5qs~#eewzV<=KxA)gQ<;upyuwSMf--PKNU<)MQ_ zg)%!Ue{{Cl?@=uXaIk-1)V~(LrS7}K1oXrqogeHq*{SzG_eA^Bq>C>Ed=TmHc`@~&_6ABrrHGm|x~+xjKC{Wc?of;CwoU7quNDPz z(IMf>XYM+@(E z+g^Tpq@kF)$nWs3_dDnPtvyX--a%GEg5`lcYf*%04%Xg|b7o@5TM3aOARX zOf2&=a;B7)a%C6;lI)je#ej!Veq3&5H=hXKA9cTeKkg*?{O<>l<|7a=M#G1G`TRS4 zUg7Uep4}zsPk8S|9FnKw&lEH8$)Pn^5lkrc1pE&?P66L##Rq|4>6N;Y;6mb_AM9yv zWTc?-pPhi;(2W<|dG;T;cemBgK`$L!ZVdR|7(8}?Y<%i)*ylj?o&?sf5HUc9FR9XZ40}#o2kD|4&_@H9taa6E*t zll`MS15TA)3S&Hg4aTvprd!?B%PR`;wx8D}f?}x-L94SYWl>eIht7NF~Q;6HTB(F=Cl58}R zm?4FdkH{3HFIi-~>r=U-+Kcla8illI9VT!X6R4!xkJ-;#g3FL;7<-TlnEN`6CGWCnf@!rY6Nv^PDiPvInM4uCemgZ+Qj$hMT&3YN;oN!` zolkO>PMv+gzVG}n@hh<6)4zpY$ml;0vXt4W0yzJM#%m39n0}tbeekQFo(}=*P4a_T z?{KA~3?by!#h(qg+F?E2{gzj#@ztS!L?W2R@?cwUWydymyNcnDnlAor*uoWnTYp<> z@Mrou!FptX0a1MG1?7kxTf!!LfPQ#B*Z~+Ir3j<{d(8W)y)5V34f==yq|cd6{4f=9 zKW0rJAAUcPbsPV|`6$jYo)<6>0?=Won{~3kx3)chW^J&l9lf z_9H(|=}#VjhkzIZL>$SlpSVCqVotUiaH%1HY=l(WdiA-O*{VWZvR1dk%)4qwaZ%qp zn#;goHbsMIX6)j-(zYn%0AyJ;x{1s%cF5p+Gp6H5{s4)2clW3WhiDBwhR5WmA9=dJ^T#Qj^q*0W0MB)?o+mv7_gV4N>9VS~ zFn(2Jz6+P)2tcD}xCFoc zfG~cGz&r@kIAoB2a{>whjNo)S>|;w4edyerU(%)f4yY_wEyn!sy4mn*QFY??ET zKE(<`wmCcdwz49JQ(^&-Uuto<6Oou+0ep;r(<5K;-)pxc_BSsl$b-k?hIdZwwX!ZSVyTE)EXd%9NH|Xx%BQ1c}+4HeYZRcUr@;gKfsfb>DXN(nF^ix9q7L zt)TiMq<^QvNp~ty?F7-t5PUhRZeC1#J{MH|P)Jn*<2%2CP!Y7rmk9}?m=|E}t9+bG z4Z;?a7e6RXahMPLb&Aj>h*!gajj@KjtCn10e$pjAeDkkh+vQ%$%Lj=~mFSId)~&eU zDvj6TEa^UlwMkSlY-2qFz6NMrH#^Y7B2Y+;uj2xqm;7>q1xyx1G0+3Ow|niw1*W+w zv)rc^cR>>Z?=vmA@O~8#8n3GO7SJ-gjO zV%mm__l>mYcc=J8PAz8DCj<`1y$=#C7{5vr+r>14H57JQi@kytcI+6lPfYTpTQSoO zs_&lGjUw>jxvm?2{dD_=PWW?pWC!m|VLi*;@)~t&&*q>E$z8o%8w_(jwzJxvu~Bv^ z5*amkm%1@s)`j`iwt{wC5^+USo*C8(8t3h~=Dx!I1m&db9V8+t!mlZ@#0>u+d~-Fd zW8~L0bd#5poR|fd4eZ;oZigC7;2_Zh7TEN}0XwC6pm)57+IbTqI=NNztHhqM!N9biBa@~=%PMStWHqBy2iQG?4(g)A#z+GY-AiIr{dT1hc72& zWzK6#z}415x1z5T`@Ud{Vqt`@chQ>>y9}qI*1AJ(=zY}m+IR_oOTC;o#jKFes8bctQBsO`3L{e z6W{-KATG?-#t}0jDZ-pL*w?rTsNSG^p7<&&J*4xZ%2_R&pBcA*Tf!@}4wxqdI=93& zx?YPXVand5Ue^}urFSc%luwa}$ zNaVabF7PJ;rO`hjuyLvR^Yvmg=2vVN1ZcEFQgp0kUQ@K&r13k+)NzRDNu%L`n_eL0 zz{B(E+l}!OB1q$K-Xr;oH*L_kta9JD(Mk)jruKF#DT4^wSJ~SPkvLs3LaY=~Wot8t z8Dw&x&}mr?$lmR3K6B0o?$feF(<%+?&9&1mWBwpfF}{ODQ$*M@__PdedRUw-3+F?s z&g$Z-iloAVjq?n3k@@(|vy_25PBJtGOsE|siW~pT$jUO7VS&YsCQev{@j z27tlf#69)zN}&V-Hz5DELYTd^9`eHU2Kht(*Zzc$53>_T=Jl70i0UJA6CQRw&b}{I zLpbr_sq@dHo_;?WrQ4{f44cx9daus0kK1+%+d&(=$LH_Om{oWeJ{#PW|AgGvlY6!! z;438~-N>&fai}|X=Kz0^SxOG|?NX4caXY8xPT#Iv=bABd_NT1c`CR3}K;^9BmX?+e zySskd8BW1G;2`TP=^Pbn=4-ur7QNicW{bS1uROQ?it%Gp3Sq@RM+=#wG$NT^P`6i+wb_Xna-Ff)&+sWk zu=^|*g_#m^uFZf6-=4c=_S1i0V4#v@+fw{tyzy%7R2>adFY|K#xgoD)!N-GhbN0du zA(KZ!olUowT?ZWY+%hj8c@WbZq+okNkk78L{)OYX8wh8xM&E?Kcn`qw$QIqs=iP9Q$C3-j{J(pDvb#X}-J zJR_}6O|$&yvDggg#Hq2d@#>PB#pR#tH)&O?sS&8$L6*DU%4)w3YvH|$0HGo-(CABJ zovD)g-h9cCgN1CxypOt8<<{EimA9AtE(BUtpe9Z8vJBg$8-;kYOx9&HFnQsEguBjeKp@HMe_ZJ}>WgdT)BXShQ+s zt1j!3!1Pp?F%*Dg@l#@5)ek()bPfw%CKS!GSrSS{r3qJ?f2;J{7+*ELX#sx(X~|=rfbW(1GTAz^1~R<&{HhQyABo+8A5ja@-8!tr z4n?iQ5M{0Y0kj=q9gSNkwVX^+YIx5QO9Phc4o>9dQ=bZLQtD$g#OoEC%7l*%yu$mb zIlN{MhYI&XL*lucS*x>r82_q5EU%K8bZF7hu2Tw~PNF0$#8udhf6D4T+!%ta#S$VU zFcG_z*rXp#qrI-#Q(~Bv$9!EyLOJLfi19*hqUm%p#TLH11Nci~cNU50fsmO5)eCoE z2yTX8;RLxXHm*B}tB>Q%$k3(DO&vtp{JA`p33F2FP3;=Un;tY8GRZQcAw41WGsjz3Ym}ORuFl{lD5&^zzUKR*z^%uaJ_R9w2T!xE#bRz^T@Y?kx&VmAHAsbRMSV?=d^^^ z_s7@q^;J<~{}ubdDN(PW(Z4cS^Q#o=Jz1 ztAWIcY1>%xrPO-RqZ7T70doh-x;uz&(+S;rRq>$`hj25Upt~D$kjHjcT-HM2;*QeE z!)y2-y-tTmH2LVK-VXEH+f|`Gc}#I>-Lgdfs>NphtB!ptjY57jYgt`P1kti6K`GD$ z&+wttc=2tEXe{8&K3&&%@^i`q=}sHO)mSq!(`+*5)+);L--!-mW-1 zmklN>h4cUah+XF*rDFWJYiS*UO!7Lhh9}Ki@RAca@76#EDGi?XA5?Cefw0ZAMv*fyZYnG%ZfBvlJ0WOt!(&xg!;Wj3d4DeF%4joFs`33k{PaW zEHY|qvMAr1b!&2Ezz^a#Zd&JVTn!wq5-l7iz3?t^mngRq;X*$xBC4Xd zv%D!9bEe(3NvVg{a4wd`bRu~cZiWs#Q~}XqVO#@83SPR?e=@WF=!K?54M3$R5hlh> z1Q~VjwzbZN(Zp=7invUBWMtH~5v&ljE>|y7u_%bs|96VYGuE%)Dr`P6X_09vidvGB zfZi0J*$67zenYe_t#P(F0|GS_+-to#I(-&7(&{N0bS-8)Ey;T|`_@Eql@=-W!NOt< zR*(ywQb+=bf3tu%cfR?cJiPg1#*W6JSA(DDt>L@v6@Ry^5Pb{ey$pECw78yNAoz

CmRirA`XK4A z3O*1t{&8~qw>dm1czbHi08y5XA+gh!k?gBuxs=br9Xiof*k*4tJH*v*nFut5B_1sd z9Pa(h*IW+fD8uS6TZxgO4s(|9q)l2wak1DyUtCMhp5^}O_W>p4@aLj{L+9ic;Hy;8 z0u*uodq?|my85?hJsrguF0LoEyWa8qWHNmPmgZ5JeR-7FH}~nao^x=#l5%nw+N`@n z@JJDj=#kpH6}GScrD{|c_T#fKKHPda(x3Y0*q~vUC0?e#&_wBSag}tjJFOGHq|^L~ zg=RZp{9!cRI?Dt`r`sQ<%fWZ_ztlaMumgd>~JE|ViN8u(X%`a)*e&DY> ztvacVh6D!ecmCsq&AY;EKZ(2Ok&p_!CO)Jh!5r?#Z({@hH?bca1g@gDO#!un=(L4P z%@Tiq8j2P&(^CCsx@k1`?M{2|#ZMCy7ZQlROo9u`J#f>1W+M4fIRwA{vz?CAMfu~+ zq2h`RReNy~+ZVi@dQ+eI_Qg)t_-HPFoceMWFy7)nJE+*WPG*WRH(^;$>buG>O>rQMChn823l0bx#*+S*{dh5f36^kTxA5l#N^A!MOF8zHe!gbr zDnmON=wy>g5qd`H&sF12ndY0Cni8JaUhK-=Umwe6i#2!nqXBZf?;9g=e-J?X44|gE JjoSBO{~tPPEQbI9 diff --git a/icons/obj/machines/nebula_shielding.dmi b/icons/obj/machines/nebula_shielding.dmi new file mode 100644 index 0000000000000000000000000000000000000000..32aedcc1b7af5cf6cecca718759eb55e66a6509f GIT binary patch literal 2779 zcma);cUV)`8^L)93fcx#ylg&OP^i-_Q5`o^)qNJ2`2z zGyni{_PecJq4|k;AmGr`btqU0nl8q=dqr3uAs;#(7#0y28Ug?@MJZV|()C6Pwi82T zivgbUx*lbE`8dpgr&is8i!$ID4fnW{X=@FHv-%So+gieJ8br!;v+ssczjf-+sxos$ zhPPOac;(OO)i`m}TQpN`l`0DQeG2r3Yz)n}|18KlTY#dw293*Ds;$(tetA2p!YsLBG05F%o<8R>%%~h`AKF(wYMGBLpW|GL5w59P~9CsBOVWI zm6Idvwff)Xe00lj;4eAy;rnm&O5+@RoD&uWPi6D2zFh>DKg>+DUzT~A{_C^SK-1p$ zn`jh_z{Wo@3j{$|rrC}i68^uZF@GdE)XZ>ZG27Q$vMtzqYG!8UG208u!v1ztgnZVq zlP@wb$Lx{?{I_Y*Nj@yfbP@SxA9tsD>g4#|jE%U0IqL{Ao@*CDzOs;6wW~xcUkw+) zunY$M;EPet>PA$+KTUXK@eNzJztK0%VCPN>f%-jKM>slLug zvKrFXe|{|kz5OsaVc2fd+`m){>uEPYZIyd0qY!(6h~}~+IZL%8W+@McWeIy5xPY&C z*Z9KVwSbkHs*@ctrLw5jccry+vpxKl5#3zpoKTz5tyL07rkX_>MRbi0IDCY@{_*pJcUS2+#cbx`RYvNmi)_28Bb4J3_g0k@w5S*T= zn5bZe7I}MvUV59I4J?|jP~rtL9F$wKhW)zTv{N)*%>Iv6Itmc+_2N+f2IapmshBgP zsx#Z4|4@GK6xHoJHnx_s6?P@iwD|s|D3@Qgjze;}7mr|&(kPuO>YG{FCtQgAi9s$E zTxLAuTr7t(VHT$8^ai3X^>f|D5KiJY^t}H?(Ttsg;B&i?Jjo+vANa8~Y0W53mVLNY zvf%P~`sDcTx5}ZX1p`fH3ZJ+qgp^@^zec?yR^CH|=0}R$H~9WEox)RiOazY$p7sJ# zQlY+;x7Q|WivLJ)8?edsi`wJlP=XX>AcJ&)@<4)U>d-4C=HEfx4_#>-aHl`HDX40& zBc>}je&|%wMhcoZVow&%siH$Cme=Ba;QyrmjLyD}C;yJW(HHzzu9J?*Gi*d}tK0(q zfUrRxQsLG$%i8zbX-A7E@_NgDi43j!Ix#si>N|Q5o#T^bZKk*U{!Zhsj z*JAzC$m`^)HBZV{FSMN@K>v)YaBLxKk2O#c-I>k`$`7zc->&Ng?PUvmPDSEntI{+OzVqzf(vez%5{zS4x2DmLXvFR+Nr)W?9oKqLxcQfQ(X=jFAOv4>ot-jfnS)h6a^tkH1Z)e&v3FU{w&kq{KTKP}I_ z(_Gs5paB#UTe&&Er~%{Of9&25sPOBl0vQd z5dVGifW<1dC`&lP^{+C%oP1X-juCEepDXjENt{$$BeI#413AU)B&sMKo0FT+BhvXG znpah8Pn zdXzN-Tb5u>87r!eVu4CK)3ez~!=&3vp3J_JF|wU6epWEC)*OTM#ONj6x=4-#MH6b6 z9UsP?^Vts&jeS7v{da3uPhGYWMGBYt+AS1Q3MKLe1Jh!3e%E8Lii9gc28M<&LjajQ??-TYX!=Qv!nvqwgoJJ3oCgOiSp(pXu}q#1f%cH*=aRb30fOG z`?er)xew)xf?i*^UQ3Ukw%isp1ie`GFsDJoU|=)}iMTq3>(sm=9AmfJ7;Tk{kRRfa z)Elmy7GFSAXq_*kKw-+@&=4V|=-G=GH-hl@ZJM=UkY2SWQk&%iRS4F62iVnaL zrA_nar1*xHh4t_UEEeYyzlhJzzEfU~%{;QI^=We)I;FLG)%PYBL$_7H-p0}TCeioQpR&C@b^rhX literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 8a92d746feb..b0817d49741 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1626,6 +1626,7 @@ #include "code\game\machinery\medipen_refiller.dm" #include "code\game\machinery\modular_shield.dm" #include "code\game\machinery\navbeacon.dm" +#include "code\game\machinery\nebula_shielding.dm" #include "code\game\machinery\PDApainter.dm" #include "code\game\machinery\prisongate.dm" #include "code\game\machinery\prisonlabor.dm"