diff --git a/code/__defines/is_helpers.dm b/code/__defines/is_helpers.dm
index fde9c8da30..54fb6f04d4 100644
--- a/code/__defines/is_helpers.dm
+++ b/code/__defines/is_helpers.dm
@@ -25,8 +25,12 @@ GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur")))
#define isradio(A) istype(A, /obj/item/radio)
+#define isclothing(A) istype(A, /obj/item/clothing)
+
#define isairlock(A) istype(A, /obj/machinery/door/airlock)
+#define ismachinery(A) istype(A, /obj/machinery)
+
#define isorgan(A) istype(A, /obj/item/organ/external)
#define isstorage(A) istype(A, /obj/item/storage)
diff --git a/code/__defines/logging.dm b/code/__defines/logging.dm
index 4ebd600bcc..ef9fb1e1cc 100644
--- a/code/__defines/logging.dm
+++ b/code/__defines/logging.dm
@@ -11,6 +11,7 @@
//Investigate logging defines
#define INVESTIGATE_CARGO "cargo"
#define INVESTIGATE_RESEARCH "research"
+#define INVESTIGATE_RADIATION "radiation"
// Logging types for log_message()
#define LOG_ATTACK (1 << 0)
diff --git a/code/__defines/obj_flags.dm b/code/__defines/obj_flags.dm
new file mode 100644
index 0000000000..0f6f1127e0
--- /dev/null
+++ b/code/__defines/obj_flags.dm
@@ -0,0 +1,8 @@
+/// Flags for specifically what kind of items to get in get_equipped_items
+#define INCLUDE_POCKETS (1<<0)
+#define INCLUDE_ACCESSORIES (1<<1)
+#define INCLUDE_HELD (1<<2)
+/// Include prosthetic item limbs (which are not flavoured as being equipped items)
+#define INCLUDE_PROSTHETICS (1<<3)
+/// Include items that are not "real" items, such as hand items
+#define INCLUDE_ABSTRACT (1<<4)
diff --git a/code/__defines/radiation.dm b/code/__defines/radiation.dm
new file mode 100644
index 0000000000..0d44b072a0
--- /dev/null
+++ b/code/__defines/radiation.dm
@@ -0,0 +1,66 @@
+/*
+These defines are the balancing points of various parts of the radiation system.
+Changes here can have widespread effects: make sure you test well.
+Ask Mothblocks if they're around
+*/
+
+/// How much stored radiation to check for hair loss
+#define RAD_MOB_HAIRLOSS (1 MINUTES)
+/// Chance of you hair starting to fall out every second when over threshold
+#define RAD_MOB_HAIRLOSS_PROB 7.5
+
+/// How much stored radiation to check for mutation
+#define RAD_MOB_MUTATE (2 MINUTES)
+/// Chance of randomly mutating every second when over threshold
+#define RAD_MOB_MUTATE_PROB 0.5
+
+/// The time since irradiated before checking for vomitting
+#define RAD_MOB_VOMIT (2 MINUTES)
+/// Chance per second of vomitting
+#define RAD_MOB_VOMIT_PROB 0.5
+
+/// How much stored radiation to check for stunning
+#define RAD_MOB_KNOCKDOWN (2 MINUTES)
+/// Chance of knockdown per second when over threshold
+#define RAD_MOB_KNOCKDOWN_PROB 0.5
+/// Amount of knockdown when it occurs
+#define RAD_MOB_KNOCKDOWN_AMOUNT 3
+
+#define RAD_NO_INSULATION 1.0 // For things that shouldn't become irradiated for whatever reason
+#define RAD_VERY_LIGHT_INSULATION 0.9 // What girders have
+#define RAD_LIGHT_INSULATION 0.8
+#define RAD_MEDIUM_INSULATION 0.7 // What common walls have
+#define RAD_HEAVY_INSULATION 0.6 // What reinforced walls have
+#define RAD_EXTREME_INSULATION 0.5 // What rad collectors have
+#define RAD_FULL_INSULATION 0 // Completely stops radiation from coming through
+
+/// The default chance something can be irradiated
+#define DEFAULT_RADIATION_CHANCE 10
+
+/// The default chance for uranium structures to irradiate
+#define URANIUM_IRRADIATION_CHANCE DEFAULT_RADIATION_CHANCE
+
+/// 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 (6 SECONDS)
+
+/// Return values of [proc/get_perceived_radiation_danger]
+// If you change these, update /datum/looping_sound/geiger as well.
+#define PERCEIVED_RADIATION_DANGER_LOW 1
+#define PERCEIVED_RADIATION_DANGER_MEDIUM 2
+#define PERCEIVED_RADIATION_DANGER_HIGH 3
+#define PERCEIVED_RADIATION_DANGER_EXTREME 4
+
+/// The time before geiger counters reset back to normal without any radiation pulses
+#define TIME_WITHOUT_RADIATION_BEFORE_RESET (5 SECONDS)
+
+// Radiation exposure params
+
+// For the radioactive nebula outside
+/// Base chance the nebula has of applying irradiation
+#define RADIATION_EXPOSURE_NEBULA_BASE_CHANCE 20
+/// The chance we add to the base chance every time we fail to irradiate
+#define RADIATION_EXPOSURE_NEBULA_CHANCE_INCREMENT 10
+/// Time it takes for the next irradiation check
+#define RADIATION_EXPOSURE_NEBULA_CHECK_INTERVAL 5 SECONDS
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index 7329808b5b..5fe7d4a2c3 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -198,3 +198,10 @@
/// The timer key used to know how long subsystem initialization takes
#define SS_INIT_TIMER_KEY "ss_init"
+
+// Subsystem delta times or tickrates, in seconds. I.e, how many seconds in between each process() call for objects being processed by that subsystem.
+// Only use these defines if you want to access some other objects processing seconds_per_tick, otherwise use the seconds_per_tick that is sent as a parameter to process()
+// #define SSFLUIDS_DT (SSplumbing.wait/10)
+#define SSMACHINES_DT (SSmachines.wait/10)
+#define SSMOBS_DT (SSmobs.wait/10)
+#define SSOBJ_DT (SSobj.wait/10)
diff --git a/code/__defines/tools.dm b/code/__defines/tools.dm
index 2ecfed08ef..f5eb3b8e5f 100644
--- a/code/__defines/tools.dm
+++ b/code/__defines/tools.dm
@@ -19,3 +19,20 @@
#define TOOL_KNIFE "knife"
#define TOOL_BLOODFILTER "bloodfilter"
#define TOOL_ROLLINGPIN "rollingpin"
+
+/**
+ * A helper for checking if an item interaction should be skipped.
+ * This is only used explicitly because some interactions may not want to ever be skipped.
+ */
+#define SHOULD_SKIP_INTERACTION(target, item, user) (HAS_TRAIT(target, TRAIT_COMBAT_MODE_SKIP_INTERACTION) && (user.a_intent == I_HURT))
+
+/// Return when an item interaction is successful.
+/// This cancels the rest of the chain entirely and indicates success.
+#define ITEM_INTERACT_SUCCESS (1<<0) // Same as TRUE, as most tool (legacy) tool acts return TRUE on success
+/// Return to prevent the rest of the attack chain from being executed / preventing the item user from thwacking the target.
+/// Similar to [ITEM_INTERACT_SUCCESS], but does not necessarily indicate success.
+#define ITEM_INTERACT_BLOCKING (1<<1)
+ /// Only for people who get confused by the naming scheme
+ #define ITEM_INTERACT_FAILURE ITEM_INTERACT_BLOCKING
+/// Return to skip the rest of the interaction chain, going straight to attack.
+#define ITEM_INTERACT_SKIP_TO_ATTACK (1<<2)
diff --git a/code/__defines/traits/declarations.dm b/code/__defines/traits/declarations.dm
index afb1fbb936..3f2fe08481 100644
--- a/code/__defines/traits/declarations.dm
+++ b/code/__defines/traits/declarations.dm
@@ -59,3 +59,35 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_SLOBBER "slobber"
// Owner will be considered a tiny mob for some interactions, such as airlocks not opening unless they have a client, or being vacuumed up by the vacpack
#define TRAIT_AMBIENT_PEST_MOB "ambient_pest_mob"
+/// Marks that this object is irradiated
+#define TRAIT_IRRADIATED "iraddiated"
+/// Whether or not this item will allow the radiation SS to go through standard
+/// radiation processing as if this wasn't already irradiated.
+/// Basically, without this, COMSIG_IN_RANGE_OF_IRRADIATION won't fire once the object is irradiated.
+#define TRAIT_BYPASS_EARLY_IRRADIATED_CHECK "radiation_bypass_early_irradiated_check"
+/// Immune to being irradiated
+#define TRAIT_RADIMMUNE "rad_immunity"
+/// This clothing protects the user from radiation.
+/// This should not be used on clothing_traits, but should be applied to the clothing itself.
+#define TRAIT_RADIATION_PROTECTED_CLOTHING "radiation_protected_clothing"
+/// Trait applied by MODsuits.
+#define MOD_TRAIT "mod"
+/// The mob has the stasis effect.
+/// Does nothing on its own, applied via status effect.
+#define TRAIT_STASIS "in_stasis"
+/// Harmful radiation effects, the toxin damage and the burns, will not occur while this trait is active
+#define TRAIT_HALT_RADIATION_EFFECTS "halt_radiation_effects"
+
+/**
+ *
+ * This trait is used in some interactions very high in the interaction chain to allow
+ * certain atoms to be skipped by said interactions if the user is in combat mode.
+ *
+ * Its primarily use case is for stuff like storage and tables, to allow things like emags to be bagged
+ * (because in some contexts you might want to be emagging a bag, and in others you might want to be storing it.)
+ *
+ * This is only checked by certain items explicitly so you can't just add the trait and expect it to work.
+ * (This may be changed later but I chose to do it this way to avoid messing up interactions which require combat mode)
+ * While /tg/ calls it combat mode, we just specifiy it as having your a_intent set to I_HURT
+ */
+#define TRAIT_COMBAT_MODE_SKIP_INTERACTION "combat_mode_skip_interaction"
diff --git a/code/__defines/turfs.dm b/code/__defines/turfs.dm
index c9a9e7f5f7..eb83eeb1c0 100644
--- a/code/__defines/turfs.dm
+++ b/code/__defines/turfs.dm
@@ -33,11 +33,13 @@
//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a
///Returns a list of turf in a square
+#define RANGE_TURFS(RADIUS, CENTER) \
+ RECT_TURFS(RADIUS, RADIUS, CENTER)
#define RECT_TURFS(H_RADIUS, V_RADIUS, CENTER) \
block( \
- locate(max(CENTER.x-(H_RADIUS),1), max(CENTER.y-(V_RADIUS),1), CENTER.z), \
- locate(min(CENTER.x+(H_RADIUS),world.maxx), min(CENTER.y+(V_RADIUS),world.maxy), CENTER.z) \
+ (CENTER).x - (H_RADIUS), (CENTER).y - (V_RADIUS), (CENTER).z, \
+ (CENTER).x + (H_RADIUS), (CENTER).y + (V_RADIUS), (CENTER).z \
)
// Wet turfs have different slipping intensities
diff --git a/code/_global_vars/traits/_traits.dm b/code/_global_vars/traits/_traits.dm
index ac7f9f3061..080074af7d 100644
--- a/code/_global_vars/traits/_traits.dm
+++ b/code/_global_vars/traits/_traits.dm
@@ -21,6 +21,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
/atom = list(
"TRAIT_ALT_CLICK_BLOCKER" = TRAIT_ALT_CLICK_BLOCKER,
),
+ /atom/movable = list(
+ "TRAIT_IRRADIATED" = TRAIT_IRRADIATED,
+ ),
/mob = list(
"TRAIT_THINKING_IN_CHARACTER" = TRAIT_THINKING_IN_CHARACTER,
"TRAIT_NOFIRE" = TRAIT_NOFIRE,
@@ -29,6 +32,10 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_TESLA_SHOCKIMMUNE" = TRAIT_TESLA_SHOCKIMMUNE,
"TRAIT_MADNESS_IMMUNE" = TRAIT_MADNESS_IMMUNE,
"TRAIT_AMBIENT_PEST_MOB" = TRAIT_AMBIENT_PEST_MOB,
+ "TRAIT_BYPASS_EARLY_IRRADIATED_CHECK" = TRAIT_BYPASS_EARLY_IRRADIATED_CHECK,
+ "TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE,
+ "TRAIT_STASIS" = TRAIT_STASIS,
+ "TRAIT_HALT_RADIATION_EFFECTS" = TRAIT_HALT_RADIATION_EFFECTS,
),
/obj = list(
"TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE,
diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm
new file mode 100644
index 0000000000..45887889a7
--- /dev/null
+++ b/code/_helpers/maths.dm
@@ -0,0 +1,45 @@
+/**
+ * Get a list of turfs in a line from `starting_atom` to `ending_atom`.
+ *
+ * Uses the ultra-fast [Bresenham Line-Drawing Algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm).
+ */
+/proc/get_line(atom/starting_atom, atom/ending_atom)
+ var/current_x_step = starting_atom.x//start at x and y, then add 1 or -1 to these to get every turf from starting_atom to ending_atom
+ var/current_y_step = starting_atom.y
+ var/starting_z = starting_atom.z
+
+ var/list/line = list(get_turf(starting_atom))//get_turf(atom) is faster than locate(x, y, z)
+
+ var/x_distance = ending_atom.x - current_x_step //x distance
+ var/y_distance = ending_atom.y - current_y_step
+
+ var/abs_x_distance = abs(x_distance)//Absolute value of x distance
+ var/abs_y_distance = abs(y_distance)
+
+ var/x_distance_sign = SIGN(x_distance) //Sign of x distance (+ or -)
+ var/y_distance_sign = SIGN(y_distance)
+
+ var/x = abs_x_distance >> 1 //Counters for steps taken, setting to distance/2
+ var/y = abs_y_distance >> 1 //Bit-shifting makes me l33t. It also makes get_line() unnecessarily fast.
+
+ if(abs_x_distance >= abs_y_distance) //x distance is greater than y
+ for(var/distance_counter in 0 to (abs_x_distance - 1))//It'll take abs_x_distance steps to get there
+ y += abs_y_distance
+
+ if(y >= abs_x_distance) //Every abs_y_distance steps, step once in y direction
+ y -= abs_x_distance
+ current_y_step += y_distance_sign
+
+ current_x_step += x_distance_sign //Step on in x direction
+ line += locate(current_x_step, current_y_step, starting_z)//Add the turf to the list
+ else
+ for(var/distance_counter in 0 to (abs_y_distance - 1))
+ x += abs_x_distance
+
+ if(x >= abs_y_distance)
+ x -= abs_y_distance
+ current_x_step += x_distance_sign
+
+ current_y_step += y_distance_sign
+ line += locate(current_x_step, current_y_step, starting_z)
+ return line
diff --git a/code/_helpers/radiation.dm b/code/_helpers/radiation.dm
new file mode 100644
index 0000000000..51ab59edec
--- /dev/null
+++ b/code/_helpers/radiation.dm
@@ -0,0 +1,85 @@
+/// Whether or not it's possible for this atom to be irradiated
+#define CAN_IRRADIATE(atom) (ismob(##atom))
+
+/// Calculates the max chance for a radiation_pulse via a radioactive reagent
+#define CALCULATE_RAD_MAX_CHANCE(rad_power) (20 + (15 * (rad_power - 1)))
+
+/// Sends out a pulse of radiation, eminating from the source.
+/// Radiation is performed by collecting all radiatables within the max range (0 means source only, 1 means adjacent, etc),
+/// then makes their way towards them. A number, starting at 1, is multiplied
+/// by the insulation amounts of whatever is in the way (for example, walls lowering it down).
+/// If this number hits equal or below the threshold, then the target can no longer be irradiated.
+/// If the number is above the threshold, then the chance is the chance that the target will be irradiated.
+/// As a consumer, this means that max_range going up usually means you want to lower the threshold too,
+/// as well as the other way around.
+/// If max_range is high, but threshold is too high, then it usually won't reach the source at the max range in time.
+/// If max_range is low, but threshold is too low, then it basically guarantees everyone nearby, even if there's walls
+/// and such in the way, can be irradiated.
+/// You can also pass in a minimum exposure time. If this is set, then this radiation pulse
+/// will not irradiate the source unless they have been around *any* radioactive source for that
+/// period of time.
+/// The chance to get irradiated diminishes over range, and from objects that block radiation.
+/// Assuming there is nothing in the way, the chance will determine what the chance is to get irradiated from half of max_range.
+/// Example: If chance is equal to 30%, and max_range is equal to 8,
+/// then the chance for a thing to get irradiated is 30% if they are 4 turfs away from the pulse source.
+/// Also, strength is how much radiation the target will get if they fail their RNG check / linger for too long.
+/proc/radiation_pulse(
+ atom/source,
+ max_range,
+ threshold,
+ chance = DEFAULT_RADIATION_CHANCE,
+ minimum_exposure_time = 0,
+ strength = 100
+)
+ if(!SSradiation.can_fire)
+ return
+
+ var/datum/radiation_pulse_information/pulse_information = new
+ pulse_information.source_ref = WEAKREF(source)
+ pulse_information.max_range = max_range
+ pulse_information.threshold = threshold
+ pulse_information.chance = chance
+ pulse_information.minimum_exposure_time = minimum_exposure_time
+ pulse_information.turfs_to_process = RANGE_TURFS(max_range, source)
+ pulse_information.strength = strength
+
+ SSradiation.processing += pulse_information
+
+ return TRUE
+
+/datum/radiation_pulse_information
+ var/datum/weakref/source_ref
+ var/max_range
+ var/threshold
+ var/chance
+ var/minimum_exposure_time
+ var/list/turfs_to_process
+ var/strength
+
+#define MEDIUM_RADIATION_THRESHOLD_RANGE 0.5
+#define EXTREME_RADIATION_CHANCE 30
+
+/// Gets the perceived "danger" of radiation pulse, given the threshold to the target.
+/// Returns a RADIATION_DANGER_* define, see [code/__DEFINES/radiation.dm]
+/proc/get_perceived_radiation_danger(datum/radiation_pulse_information/pulse_information, insulation_to_target)
+ if (insulation_to_target > pulse_information.threshold)
+ // We could get irradiated! The only thing stopping us now is chance, so scale based on that.
+ if (pulse_information.chance >= EXTREME_RADIATION_CHANCE)
+ return PERCEIVED_RADIATION_DANGER_EXTREME
+ else
+ return PERCEIVED_RADIATION_DANGER_HIGH
+ else
+ // We're out of the threshold from being irradiated, but by how much?
+ if (insulation_to_target / pulse_information.threshold <= MEDIUM_RADIATION_THRESHOLD_RANGE)
+ return PERCEIVED_RADIATION_DANGER_MEDIUM
+ else
+ return PERCEIVED_RADIATION_DANGER_LOW
+
+/// A common proc used to send COMSIG_ATOM_PROPAGATE_RAD_PULSE to adjacent atoms
+/// Only used for uranium (false/tram)walls to spread their radiation pulses
+/atom/proc/propagate_radiation_pulse()
+ for(var/atom/atom in orange(1,src))
+ SEND_SIGNAL(atom, COMSIG_ATOM_PROPAGATE_RAD_PULSE, src)
+
+#undef MEDIUM_RADIATION_THRESHOLD_RANGE
+#undef EXTREME_RADIATION_CHANCE
diff --git a/code/_helpers/spatial_info.dm b/code/_helpers/spatial_info.dm
new file mode 100644
index 0000000000..d385b01e07
--- /dev/null
+++ b/code/_helpers/spatial_info.dm
@@ -0,0 +1,8 @@
+///Returns the distance between two atoms
+/proc/get_dist_euclidean(atom/first_location, atom/second_location)
+ var/dx = first_location.x - second_location.x
+ var/dy = first_location.y - second_location.y
+
+ var/dist = sqrt(dx ** 2 + dy ** 2)
+
+ return dist
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index 87af9ad301..e0b04ef991 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -7,13 +7,6 @@
//Checks if all high bits in req_mask are set in bitfield
#define BIT_TEST_ALL(bitfield, req_mask) ((~(bitfield) & (req_mask)) == 0)
-//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a
-#define RANGE_TURFS(RADIUS, CENTER) \
- block( \
- locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \
- locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
- )
-
//Returns the middle-most value
/proc/dd_range(var/low, var/high, var/num)
return max(low,min(high,num))
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 94d86e696a..5bbbc60ddf 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -307,6 +307,11 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
var/mob/living/L = usr
return L.resist()
+/atom/movable/screen/alert/irradiated
+ name = "Irradiated"
+ desc = "You're irradiated! Heal your radiation quick, and stand under a shower to wash some radiation off from yourself!"
+// use_user_hud_icon = TRUE
+ icon_state = "irradiated"
//ALIENS
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 75eb19fd22..7aa95af384 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -603,40 +603,6 @@
/datum/config_entry/str_list/language_prefixes
default = list(",", "#", "-")
-// 0:1 subtraction:division for computing effective radiation on a turf
-/// 0 / RAD_RESIST_CALC_DIV = Each turf absorbs some fraction of the working radiation level
-/// 1 / RAD_RESIST_CALC_SUB = Each turf absorbs a fixed amount of radiation
-/datum/config_entry/flag/radiation_resistance_calc_mode
- default = RAD_RESIST_CALC_SUB
-
-/datum/config_entry/flag/radiation_resistance_calc_mode/ValidateAndSet(str_val)
- if(!VASProcCallGuard(str_val))
- return FALSE
- var/val_as_num = text2num(str_val)
- if(val_as_num in list(RAD_RESIST_CALC_DIV, RAD_RESIST_CALC_SUB))
- config_entry_value = val_as_num
- return TRUE
- return FALSE
-
-///How much radiation is reduced by each tick
-/datum/config_entry/number/radiation_decay_rate
- default = 1
- integer = FALSE
-
-/datum/config_entry/number/radiation_resistance_multiplier
- default = 8.5 //VOREstation edit
- integer = FALSE
-
-/datum/config_entry/number/radiation_material_resistance_divisor
- default = 1
- min_val = 0.1
- integer = FALSE
-
-///If the radiation level for a turf would be below this, ignore it.
-/datum/config_entry/number/radiation_lower_limit
- default = 0.35
- integer = FALSE
-
/// If true, submaps loaded automatically can be rotated.
/datum/config_entry/flag/random_submap_orientation
diff --git a/code/controllers/subsystems/irradiated.dm b/code/controllers/subsystems/irradiated.dm
new file mode 100644
index 0000000000..b634a1b6ef
--- /dev/null
+++ b/code/controllers/subsystems/irradiated.dm
@@ -0,0 +1,208 @@
+#define RADIATION_IMMEDIATE_TOX_DAMAGE 10
+
+#define RADIATION_TOX_DAMAGE_PER_INTERVAL 2
+#define RADIATION_TOX_INTERVAL (25 SECONDS)
+
+#define RADIATION_BURN_SPLOTCH_DAMAGE 11
+#define RADIATION_BURN_INTERVAL_MIN (30 SECONDS)
+#define RADIATION_BURN_INTERVAL_MAX (60 SECONDS)
+
+// Showers process on SSmachines
+#define RADIATION_CLEAN_IMMUNITY_TIME (SSMACHINES_DT + (1 SECONDS))
+
+/// This atom is irradiated, and will glow green.
+/// Humans will take toxin damage until all their toxin damage is cleared.
+/datum/component/irradiated
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+ var/beginning_of_irradiation
+
+ var/burn_splotch_timer_id
+
+ COOLDOWN_DECLARE(clean_cooldown)
+ COOLDOWN_DECLARE(last_tox_damage)
+
+/datum/component/irradiated/Initialize()
+ if (!CAN_IRRADIATE(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ // This isn't incompatible, it's just wrong
+ if (HAS_TRAIT(parent, TRAIT_RADIMMUNE))
+ qdel(src)
+ return
+
+ ADD_TRAIT(parent, TRAIT_IRRADIATED, REF(src))
+
+ create_glow()
+
+ beginning_of_irradiation = world.time
+
+ if (ishuman(parent))
+ var/mob/living/carbon/human/human_parent = parent
+ human_parent.apply_damage(RADIATION_IMMEDIATE_TOX_DAMAGE, TOX)
+ START_PROCESSING(SSobj, src)
+
+ COOLDOWN_START(src, last_tox_damage, RADIATION_TOX_INTERVAL)
+
+ start_burn_splotch_timer()
+
+ human_parent.throw_alert("irradiated", /atom/movable/screen/alert/irradiated)
+
+/datum/component/irradiated/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
+ RegisterSignal(parent, COMSIG_GEIGER_COUNTER_SCAN, PROC_REF(on_geiger_counter_scan))
+ RegisterSignal(parent, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_healthscan))
+
+/datum/component/irradiated/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_COMPONENT_CLEAN_ACT,
+ COMSIG_GEIGER_COUNTER_SCAN,
+ COMSIG_LIVING_HEALTHSCAN,
+ ))
+
+/datum/component/irradiated/Destroy(force)
+ var/atom/movable/parent_movable = parent
+ if (istype(parent_movable))
+ parent_movable.remove_filter("rad_glow")
+
+ var/mob/living/carbon/human/human_parent = parent
+ if (istype(human_parent))
+ human_parent.clear_alert("irradiated")
+
+ REMOVE_TRAIT(parent, TRAIT_IRRADIATED, REF(src))
+
+ deltimer(burn_splotch_timer_id)
+ STOP_PROCESSING(SSobj, src)
+
+ return ..()
+
+/datum/component/irradiated/process(seconds_per_tick)
+ if (!ishuman(parent))
+ return PROCESS_KILL
+
+ if (HAS_TRAIT(parent, TRAIT_RADIMMUNE))
+ qdel(src)
+ return PROCESS_KILL
+
+ var/mob/living/carbon/human/human_parent = parent
+ if (human_parent.radiation == 0)
+ qdel(src)
+ return PROCESS_KILL
+
+ if (should_halt_effects(parent))
+ return
+
+
+// if (human_parent.stat != DEAD)
+// human_parent.handle_radiation()
+
+// human_parent.dna?.species?.handle_radiation(human_parent, world.time - beginning_of_irradiation, seconds_per_tick)
+
+// process_tox_damage(human_parent, seconds_per_tick)
+
+/datum/component/irradiated/proc/should_halt_effects(mob/living/carbon/human/target)
+ if (HAS_TRAIT(target, TRAIT_STASIS))
+ return TRUE
+
+ if (HAS_TRAIT(target, TRAIT_HALT_RADIATION_EFFECTS))
+ return TRUE
+
+ if (!COOLDOWN_FINISHED(src, clean_cooldown))
+ return TRUE
+
+ return FALSE
+
+/datum/component/irradiated/proc/process_tox_damage(mob/living/carbon/human/target, seconds_per_tick)
+ if (!COOLDOWN_FINISHED(src, last_tox_damage))
+ return
+
+ target.apply_damage(RADIATION_TOX_DAMAGE_PER_INTERVAL, TOX)
+ COOLDOWN_START(src, last_tox_damage, RADIATION_TOX_INTERVAL)
+
+/datum/component/irradiated/proc/start_burn_splotch_timer()
+ addtimer(CALLBACK(src, PROC_REF(give_burn_splotches)), rand(RADIATION_BURN_INTERVAL_MIN, RADIATION_BURN_INTERVAL_MAX), TIMER_STOPPABLE)
+
+/datum/component/irradiated/proc/give_burn_splotches()
+ // This shouldn't be possible, but just in case.
+ if (QDELETED(src))
+ return
+
+ start_burn_splotch_timer()
+
+ var/mob/living/carbon/human/human_parent = parent
+
+ if (should_halt_effects(parent))
+ return
+
+ var/obj/item/organ/external/affected_limb = pick(human_parent.organs)
+ human_parent.visible_message(
+ span_boldwarning("[human_parent]'s [affected_limb.name] bubbles unnaturally, then bursts into blisters!"),
+ span_boldwarning("Your [affected_limb.name] bubbles unnaturally, then bursts into blisters!"),
+ )
+
+ if(human_parent.is_blind())
+ to_chat(human_parent, span_boldwarning("Your [affected_limb.name] feels like it's bubbling, then burns like hell!"))
+
+ human_parent.apply_damage(RADIATION_BURN_SPLOTCH_DAMAGE, BURN, affected_limb, blocked = FALSE, used_weapon = "Radiation Burns")
+ playsound(
+ human_parent,
+ get_sfx("sizzle"),
+ 50,
+ vary = TRUE,
+ )
+
+/datum/component/irradiated/proc/create_glow()
+ var/atom/movable/parent_movable = parent
+ if (!istype(parent_movable))
+ return
+
+ parent_movable.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#39ff1430", "size" = 2))
+ addtimer(CALLBACK(src, PROC_REF(start_glow_loop), parent_movable), rand(0.1 SECONDS, 1.9 SECONDS)) // Things should look uneven
+
+/datum/component/irradiated/proc/start_glow_loop(atom/movable/parent_movable)
+ var/filter = parent_movable.get_filter("rad_glow")
+ if (!filter)
+ return
+
+ animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1)
+ animate(alpha = 40, time = 2.5 SECONDS)
+
+/datum/component/irradiated/proc/on_clean(datum/source, clean_types)
+ SIGNAL_HANDLER
+
+ if (!(clean_types & CLEAN_TYPE_RADIATION))
+ return NONE
+
+ if (isitem(parent))
+ qdel(src)
+ return COMPONENT_CLEANED|COMPONENT_CLEANED_GAIN_XP
+
+ COOLDOWN_START(src, clean_cooldown, RADIATION_CLEAN_IMMUNITY_TIME)
+
+/datum/component/irradiated/proc/on_geiger_counter_scan(datum/source, mob/user, obj/item/geiger/geiger_counter)
+ SIGNAL_HANDLER
+
+ if (isliving(source))
+ var/mob/living/living_source = source
+ to_chat(user, span_bolddanger("[icon2html(geiger_counter, user)] Subject is irradiated. Contamination traces back to roughly [DisplayTimeText(world.time - beginning_of_irradiation, 5)] ago. Current toxin levels: [living_source.getToxLoss()]."))
+ else
+ // In case the green wasn't obvious enough...
+ to_chat(user, span_bolddanger("[icon2html(geiger_counter, user)] Target is irradiated."))
+
+ return COMSIG_GEIGER_COUNTER_SCAN_SUCCESSFUL
+
+/datum/component/irradiated/proc/on_healthscan(datum/source, list/render_list, advanced, mob/user, mode, tochat)
+ SIGNAL_HANDLER
+ render_list += span_notice("Subject is irradiated. Supply antiradiation or antitoxin, such as [/datum/reagent/prussian_blue::name].")
+
+// render_list += ""
+ //render_list += conditional_tooltip("Subject is irradiated.", "Supply antiradiation or antitoxin, such as [/datum/reagent/prussian_blue::name].", tochat)
+// render_list += "
"
+
+#undef RADIATION_BURN_SPLOTCH_DAMAGE
+#undef RADIATION_BURN_INTERVAL_MIN
+#undef RADIATION_BURN_INTERVAL_MAX
+#undef RADIATION_CLEAN_IMMUNITY_TIME
+#undef RADIATION_IMMEDIATE_TOX_DAMAGE
+#undef RADIATION_TOX_INTERVAL
+#undef RADIATION_TOX_DAMAGE_PER_INTERVAL
diff --git a/code/controllers/subsystems/radiation.dm b/code/controllers/subsystems/radiation.dm
index 165c2120ef..7c7af986e7 100644
--- a/code/controllers/subsystems/radiation.dm
+++ b/code/controllers/subsystems/radiation.dm
@@ -1,148 +1,186 @@
SUBSYSTEM_DEF(radiation)
name = "Radiation"
- wait = 2 SECONDS
- flags = SS_NO_INIT
+ flags = SS_BACKGROUND | SS_NO_INIT
- var/list/sources = list() // all radiation source datums
- var/list/sources_assoc = list() // Sources indexed by turf for de-duplication.
- var/list/resistance_cache = list() // Cache of turf's radiation resistance.
+ wait = 0.5 SECONDS
- var/tmp/list/current_sources = list()
- var/tmp/list/current_res_cache = list()
- var/tmp/list/listeners = list()
+ /// A list of radiation sources (/datum/radiation_pulse_information) that have yet to process.
+ /// Do not interact with this directly, use `radiation_pulse` instead.
+ var/list/datum/radiation_pulse_information/processing = list()
-/datum/controller/subsystem/radiation/fire(resumed = FALSE)
- if (!resumed)
- current_sources = sources.Copy()
- current_res_cache = resistance_cache.Copy()
- listeners = GLOB.living_mob_list.Copy()
+/datum/controller/subsystem/radiation/fire(resumed)
+ while (processing.len)
+ var/datum/radiation_pulse_information/pulse_information = processing[1]
- while(length(current_sources))
- var/datum/radiation_source/S = current_sources[length(current_sources)]
- current_sources.len--
+ var/datum/weakref/source_ref = pulse_information.source_ref
+ var/atom/source = source_ref.resolve()
+ if (isnull(source))
+ processing.Cut(1, 2)
+ continue
+
+ pulse(source, pulse_information)
- if(QDELETED(S))
- sources -= S
- else if(S.decay)
- S.update_rad_power(S.rad_power - CONFIG_GET(number/radiation_decay_rate))
if (MC_TICK_CHECK)
return
- while(length(current_res_cache))
- var/turf/T = current_res_cache[length(current_res_cache)]
- current_res_cache.len--
-
- if(QDELETED(T))
- resistance_cache -= T
- else if((length(T.contents) + 1) != resistance_cache[T])
- resistance_cache -= T // If its stale REMOVE it! It will get added if its needed.
- if (MC_TICK_CHECK)
- return
-
- if(!length(sources))
- listeners.Cut()
-
- while(length(listeners))
- var/atom/A = listeners[length(listeners)]
- listeners.len--
-
- if(!QDELETED(A))
- var/turf/T = get_turf(A)
- var/rads = get_rads_at_turf(T)
- if(rads)
- A.rad_act(rads)
- if (MC_TICK_CHECK)
- return
+ processing.Cut(1, 2)
/datum/controller/subsystem/radiation/stat_entry(msg)
- msg = "S:[length(sources)], RC:[length(resistance_cache)]"
+ msg = "Pulses:[processing.len]"
return ..()
-// Ray trace from all active radiation sources to T and return the strongest effect.
-/datum/controller/subsystem/radiation/proc/get_rads_at_turf(var/turf/T)
- . = 0
- if(!istype(T))
- return
+/datum/controller/subsystem/radiation/proc/pulse(atom/source, datum/radiation_pulse_information/pulse_information)
+ var/list/cached_rad_insulations = list()
+ var/list/cached_turfs_to_process = pulse_information.turfs_to_process
+ var/turfs_iterated = 0
+ var/pulse_strength = pulse_information.strength
+ for (var/turf/turf_to_irradiate as anything in cached_turfs_to_process)
+ turfs_iterated += 1
- for(var/datum/radiation_source/source as anything in sources)
- if(source.rad_power < .)
- continue // Already being affected by a stronger source
+ for(var/obj/machinery/power/rad_collector in turf_to_irradiate)
+ SEND_SIGNAL(rad_collector, COMSIG_IN_RANGE_OF_IRRADIATION, pulse_information, 1) //We just do it here and skip all the math to make it faster. Sure, we could have something blocking the rad collectors, but this is faster and has better CPU gains in exchange for negligible gameplay impact.
+ continue
- if(source.source_turf.z != T.z)
- continue // Radiation is not multi-z
+ for(var/obj/item/geiger/geiger_counter in turf_to_irradiate)
+ var/current_insulation = 1
+ for(var/turf/turf_in_between in get_line(source, geiger_counter) - get_turf(source))
+ var/insulation = cached_rad_insulations[turf_in_between]
+ if(isnull(insulation))
+ insulation = turf_in_between.rad_insulation
+ for (var/atom/on_turf as anything in turf_in_between.contents)
+ insulation *= on_turf.rad_insulation
+ cached_rad_insulations[turf_in_between] = insulation
- if(source.respect_maint)
- var/area/A = T.loc
- if(A.flag_check(RAD_SHIELDED))
- continue // In shielded area
+ current_insulation *= insulation
- var/dist = get_dist(source.source_turf, T)
- if(dist > source.range)
- continue // Too far to possibly affect
+ if(current_insulation <= pulse_information.threshold)
+ continue
- if(source.flat)
- . += source.rad_power
- continue // No need to ray trace for flat field
+ SEND_SIGNAL(geiger_counter, COMSIG_IN_RANGE_OF_IRRADIATION, pulse_information, current_insulation)
- // Okay, now ray trace to find resistence!
- var/turf/origin = source.source_turf
- var/working = source.rad_power
- while(origin != T)
- origin = get_step_towards(origin, T) //Raytracing
- if(!resistance_cache[origin]) //Only get the resistance if we don't already know it.
- origin.calc_rad_resistance()
- if(origin.cached_rad_resistance)
- if(CONFIG_GET(flag/radiation_resistance_calc_mode) == RAD_RESIST_CALC_DIV)
- working = round((working / (origin.cached_rad_resistance * CONFIG_GET(number/radiation_resistance_multiplier))), 0.01)
- else if(CONFIG_GET(flag/radiation_resistance_calc_mode) == RAD_RESIST_CALC_SUB)
- working = round((working - (origin.cached_rad_resistance * CONFIG_GET(number/radiation_resistance_multiplier))), 0.01)
+ for(var/mob/living/target in turf_to_irradiate)
+ if(!can_irradiate_basic(target))
+ continue
- if(working <= CONFIG_GET(number/radiation_lower_limit)) // Too far from this source
- working = 0 // May as well be 0
+ var/current_insulation = 1
+ for (var/turf/turf_in_between in get_line(source, target) - get_turf(source))
+ var/insulation = cached_rad_insulations[turf_in_between]
+ if (isnull(insulation))
+ insulation = turf_in_between.rad_insulation
+ for (var/atom/on_turf as anything in turf_in_between.contents)
+ insulation *= on_turf.rad_insulation
+ cached_rad_insulations[turf_in_between] = insulation
+
+ current_insulation *= insulation
+
+ if (current_insulation <= pulse_information.threshold)
+ break
+
+ SEND_SIGNAL(target, COMSIG_IN_RANGE_OF_IRRADIATION, pulse_information, current_insulation)
+
+ // Check a second time, because of TRAIT_BYPASS_EARLY_IRRADIATED_CHECK
+ if (HAS_TRAIT(target, TRAIT_IRRADIATED))
+ continue
+
+ if (current_insulation <= pulse_information.threshold)
+ continue
+
+ /// Perceived chance of target getting irradiated.
+ var/perceived_chance
+ /// Intensity variable which will describe the radiation pulse.
+ /// It is used by perceived intensity, which diminishes over range. The chance of the target getting irradiated is determined by perceived_intensity.
+ /// Intensity is calculated so that the chance of getting irradiated at half of the max range is the same as the chance parameter.
+ var/intensity
+ /// Diminishes over range. Used by perceived chance, which is the actual chance to get irradiated.
+ var/perceived_intensity
+
+ if(pulse_information.chance < 100) // Prevents log(0) runtime if chance is 100%
+ intensity = -log(1 - pulse_information.chance / 100) * (1 + pulse_information.max_range / 2) ** 2
+ perceived_intensity = intensity * INVERSE((1 + get_dist_euclidean(source, target)) ** 2) // Diminishes over range.
+ perceived_intensity *= (current_insulation - pulse_information.threshold) * INVERSE(1 - pulse_information.threshold) // Perceived intensity decreases as objects that absorb radiation block its trajectory.
+ perceived_chance = 100 * (1 - NUM_E ** -perceived_intensity)
+ pulse_strength = pulse_strength * (1 - NUM_E ** -perceived_intensity)
+ else
+ perceived_chance = 100
+
+ var/irradiation_result = SEND_SIGNAL(target, COMSIG_IN_THRESHOLD_OF_IRRADIATION, pulse_information)
+ if (irradiation_result & CANCEL_IRRADIATION)
+ continue
+
+ if (pulse_information.minimum_exposure_time && !(irradiation_result & SKIP_MINIMUM_EXPOSURE_TIME_CHECK))
+ target.AddComponent(/datum/component/radiation_countdown, pulse_information.minimum_exposure_time)
+ continue
+
+ if (!prob(perceived_chance))
+ continue
+
+ if (irradiate_after_basic_checks(target, pulse_strength))
+ target.investigate_log("was irradiated by [source].", INVESTIGATE_RADIATION)
+
+ if(MC_TICK_CHECK)
+ break
+
+ cached_turfs_to_process.Cut(1, turfs_iterated + 1)
+
+/// Will attempt to irradiate the given target, limited through IC means, such as radiation protected clothing.
+/datum/controller/subsystem/radiation/proc/irradiate(atom/target, strength)
+ if (!can_irradiate_basic(target))
+ return FALSE
+
+ irradiate_after_basic_checks(target, strength)
+ return TRUE
+
+/datum/controller/subsystem/radiation/proc/irradiate_after_basic_checks(mob/living/target, strength)
+ PRIVATE_PROC(TRUE)
+
+ if(!ishuman(target))
+ if(ismob(target))
+ target.radiation += strength
+ return TRUE
+ return FALSE
+
+ /// 0 = full protection, 1 = no protection.
+ var/rad_vulnerability = 1 - wearing_rad_protected_clothing(target)
+ if(rad_vulnerability <= 0)
+ return FALSE
+ target.radiation += round(strength * rad_vulnerability, 0.1)
+
+// target.AddComponent(/datum/component/irradiated)
+ return TRUE
+
+/// Returns whether or not the target can be irradiated by any means.
+/// Does not check for clothing.
+/datum/controller/subsystem/radiation/proc/can_irradiate_basic(atom/target)
+ if (!CAN_IRRADIATE(target))
+ return FALSE
+
+ if (HAS_TRAIT(target, TRAIT_IRRADIATED) && !HAS_TRAIT(target, TRAIT_BYPASS_EARLY_IRRADIATED_CHECK))
+ return FALSE
+
+ if (HAS_TRAIT(target, TRAIT_RADIMMUNE))
+ return FALSE
+
+ return TRUE
+
+/// Retruns a value from 1 (full protection) to 0 (no protection)
+/// If we have 4 limbs and 3 are protected, we would expect to have 0.75 returned.
+/datum/controller/subsystem/radiation/proc/wearing_rad_protected_clothing(mob/living/carbon/human/human)
+ ///Check how many limbs we have.
+ var/limb_count = 0
+ ///Check how many of our limbs are protected.
+ var/protected_limbs = 0
+ for(var/obj/item/organ/external/limb as anything in human.organs)
+ limb_count++
+
+ for(var/obj/item/clothing as anything in human.get_clothing_on_part(limb))
+ if(HAS_TRAIT(clothing, TRAIT_RADIATION_PROTECTED_CLOTHING)) //If our clothing
+ protected_limbs++
break
- // Accumulate radiation from all sources in range, not just the biggest.
- // Shouldn't really ever have practical uses, but standing in a room literally made from uranium is more dangerous than standing next to a single uranium vase
- . += working / (dist ** 2)
+ var/rad_resistance = clothing.armor["rad"]
+ if(prob(rad_resistance))
+ protected_limbs++
+ break
- if(. <= CONFIG_GET(number/radiation_lower_limit))
- . = 0
-
-// Add a radiation source instance to the repository. It will override any existing source on the same turf.
-/datum/controller/subsystem/radiation/proc/add_source(var/datum/radiation_source/S)
- if(!isturf(S.source_turf))
- return
- var/datum/radiation_source/existing = sources_assoc[S.source_turf]
- if(existing)
- qdel(existing)
- sources += S
- sources_assoc[S.source_turf] = S
-
-// Creates a temporary radiation source that will decay
-/datum/controller/subsystem/radiation/proc/radiate(source, power) //Sends out a radiation pulse, taking walls into account
- if(!(source && power)) //Sanity checking
- return
- var/datum/radiation_source/S = new()
- S.source_turf = get_turf(source)
- S.update_rad_power(power)
- add_source(S)
-
-// Sets the radiation in a range to a constant value.
-/datum/controller/subsystem/radiation/proc/flat_radiate(source, power, range, var/respect_maint = TRUE) //VOREStation edit; Respect shielded areas by default please.
- if(!(source && power && range))
- return
- var/datum/radiation_source/S = new()
- S.flat = TRUE
- S.range = range
- S.respect_maint = respect_maint
- S.source_turf = get_turf(source)
- S.update_rad_power(power)
- add_source(S)
-
-// Irradiates a full Z-level. Hacky way of doing it, but not too expensive.
-/datum/controller/subsystem/radiation/proc/z_radiate(var/atom/source, power, var/respect_maint = TRUE) //VOREStation edit; Respect shielded areas by default please.
- if(!(power && source))
- return
- var/turf/epicentre = locate(round(world.maxx / 2), round(world.maxy / 2), source.z)
- flat_radiate(epicentre, power, world.maxx, respect_maint)
+ return (protected_limbs/limb_count)
diff --git a/code/datums/components/geiger_sound.dm b/code/datums/components/geiger_sound.dm
new file mode 100644
index 0000000000..7bfa16d0cc
--- /dev/null
+++ b/code/datums/components/geiger_sound.dm
@@ -0,0 +1,92 @@
+/// Atoms with this component will play sounds depending on nearby radiation
+/datum/component/geiger_sound
+ var/datum/looping_sound/geiger/sound
+
+ var/last_parent = null
+
+/datum/component/geiger_sound/Initialize(...)
+ if (!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/geiger_sound/Destroy(force)
+ QDEL_NULL(sound)
+
+ if (!isnull(last_parent))
+ UnregisterSignal(last_parent, COMSIG_IN_RANGE_OF_IRRADIATION)
+
+ last_parent = null
+
+ return ..()
+
+/datum/component/geiger_sound/RegisterWithParent()
+ sound = new(list(parent), TRUE)
+
+ RegisterSignal(parent, COMSIG_IN_RANGE_OF_IRRADIATION, PROC_REF(on_pre_potential_irradiation))
+
+ ADD_TRAIT(parent, TRAIT_BYPASS_EARLY_IRRADIATED_CHECK, REF(src))
+
+ if (isitem(parent))
+ var/atom/atom_parent = parent
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ register_to_loc(atom_parent.loc)
+
+/datum/component/geiger_sound/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_IN_RANGE_OF_IRRADIATION,
+ ))
+
+ REMOVE_TRAIT(parent, TRAIT_BYPASS_EARLY_IRRADIATED_CHECK, REF(src))
+
+/datum/component/geiger_sound/proc/on_pre_potential_irradiation(datum/source, datum/radiation_pulse_information/pulse_information, insulation_to_target)
+ SIGNAL_HANDLER
+
+ sound.last_insulation_to_target = insulation_to_target
+ sound.last_radiation_pulse = pulse_information
+ sound.start(source)
+
+ addtimer(CALLBACK(sound, TYPE_PROC_REF(/datum/looping_sound,stop)), TIME_WITHOUT_RADIATION_BEFORE_RESET, TIMER_UNIQUE | TIMER_OVERRIDE)
+
+/datum/component/geiger_sound/proc/on_moved(atom/source)
+ SIGNAL_HANDLER
+ register_to_loc(source.loc)
+
+/datum/component/geiger_sound/proc/register_to_loc(new_loc)
+ if (last_parent == new_loc)
+ return
+
+ if (!isnull(last_parent))
+ UnregisterSignal(last_parent, COMSIG_IN_RANGE_OF_IRRADIATION)
+
+ last_parent = new_loc
+
+ if (!isnull(new_loc))
+ RegisterSignal(new_loc, COMSIG_IN_RANGE_OF_IRRADIATION, PROC_REF(on_pre_potential_irradiation))
+
+/datum/looping_sound/geiger
+ mid_sounds = list(
+ list('sound/items/geiger/low1.ogg'=1, 'sound/items/geiger/low2.ogg'=1, 'sound/items/geiger/low3.ogg'=1, 'sound/items/geiger/low4.ogg'=1),
+ list('sound/items/geiger/med1.ogg'=1, 'sound/items/geiger/med2.ogg'=1, 'sound/items/geiger/med3.ogg'=1, 'sound/items/geiger/med4.ogg'=1),
+ list('sound/items/geiger/high1.ogg'=1, 'sound/items/geiger/high2.ogg'=1, 'sound/items/geiger/high3.ogg'=1, 'sound/items/geiger/high4.ogg'=1),
+ list('sound/items/geiger/ext1.ogg'=1, 'sound/items/geiger/ext2.ogg'=1, 'sound/items/geiger/ext3.ogg'=1, 'sound/items/geiger/ext4.ogg'=1)
+ )
+ mid_length = 2
+ volume = 25
+
+ var/datum/radiation_pulse_information/last_radiation_pulse
+ var/last_insulation_to_target
+
+/datum/looping_sound/geiger/Destroy()
+ last_radiation_pulse = null
+ return ..()
+
+/datum/looping_sound/geiger/get_sound()
+ if (isnull(last_radiation_pulse))
+ return null
+
+ return ..(mid_sounds[get_perceived_radiation_danger(last_radiation_pulse, last_insulation_to_target)])
+
+/datum/looping_sound/geiger/stop(null_parent = FALSE)
+ . = ..()
+
+ last_radiation_pulse = null
diff --git a/code/datums/components/radiation_countdown.dm b/code/datums/components/radiation_countdown.dm
new file mode 100644
index 0000000000..a328917839
--- /dev/null
+++ b/code/datums/components/radiation_countdown.dm
@@ -0,0 +1,55 @@
+// Should be more than any minimum exposure time coming in
+#define TIME_UNTIL_DELETION (10 SECONDS)
+
+/// Begins the countdown before a target can be irradiated.
+/// Added by the radiation subsystem when a pulse information has a minimum exposure time.
+/// Will clear itself out after a while.
+/datum/component/radiation_countdown
+ /// The time this component was added
+ var/time_added
+
+ /// The shortest minimum time before being irradiated.
+ /// If the source has an attempted irradiation again outside this timeframe, it will go through.
+ var/minimum_exposure_time
+
+/datum/component/radiation_countdown/Initialize(minimum_exposure_time)
+ if (!CAN_IRRADIATE(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.minimum_exposure_time = minimum_exposure_time
+
+ time_added = world.time
+
+// to_chat(parent, span_userdanger("The air around you feels warm...perhaps you should go somewhere else.")) //Silent.
+
+ start_deletion_timer()
+
+/datum/component/radiation_countdown/proc/start_deletion_timer()
+ addtimer(CALLBACK(src, PROC_REF(remove_self)), TIME_UNTIL_DELETION, TIMER_UNIQUE | TIMER_OVERRIDE)
+
+/datum/component/radiation_countdown/proc/remove_self()
+// if (!HAS_TRAIT(parent, TRAIT_IRRADIATED))
+// to_chat(parent, span_notice("The air here feels safer."))
+
+ qdel(src)
+
+/datum/component/radiation_countdown/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_IN_THRESHOLD_OF_IRRADIATION, PROC_REF(on_pre_potential_irradiation_within_range))
+
+/datum/component/radiation_countdown/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_IN_THRESHOLD_OF_IRRADIATION)
+
+/datum/component/radiation_countdown/proc/on_pre_potential_irradiation_within_range(datum/source, datum/radiation_pulse_information/pulse_information)
+ SIGNAL_HANDLER
+
+ minimum_exposure_time = min(minimum_exposure_time, pulse_information.minimum_exposure_time)
+
+ start_deletion_timer()
+
+ // Played with fire, now you might be getting irradiated.
+ if (world.time - time_added >= minimum_exposure_time)
+ return SKIP_MINIMUM_EXPOSURE_TIME_CHECK
+
+ return CANCEL_IRRADIATION
+
+#undef TIME_UNTIL_DELETION
diff --git a/code/datums/components/radioactive_exposure.dm b/code/datums/components/radioactive_exposure.dm
new file mode 100644
index 0000000000..914f4ce0fe
--- /dev/null
+++ b/code/datums/components/radioactive_exposure.dm
@@ -0,0 +1,79 @@
+/// For directly applying to carbons to irradiate them, without pulses
+/datum/component/radioactive_exposure
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+
+ /// Base irradiation chance
+ var/irradiation_chance_base
+ /// Chance we have of applying irradiation
+ var/irradiation_chance
+ /// The amount the base chance is increased after every failed irradiation check
+ var/irradiation_chance_increment
+ /// Time till we attempt the next irradiation check
+ var/irradiation_interval
+ /// The source of irradiation, for logging
+ var/source
+ /// Area's where the component isnt removed if we cross to them
+ var/list/radioactive_areas
+
+/datum/component/radioactive_exposure/Initialize(
+ minimum_exposure_time,
+ irradiation_chance_base,
+ irradiation_chance_increment,
+ irradiation_interval,
+ source,
+ radioactive_areas
+ )
+
+ if(!iscarbon(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.irradiation_chance_base = irradiation_chance_base
+ src.irradiation_chance = irradiation_chance_base
+ src.irradiation_chance_increment = irradiation_chance_increment
+ src.irradiation_interval = irradiation_interval
+ src.source = source
+ src.radioactive_areas = radioactive_areas
+
+ // We use generally long times, so it's probably easier and more interpretable to just use a timer instead of processing the component
+ addtimer(CALLBACK(src, PROC_REF(attempt_irradiate)), minimum_exposure_time)
+
+ RegisterSignal(parent, COMSIG_MOVABLE_EXITED_AREA, PROC_REF(on_exited))
+
+ var/mob/living/living_parent = parent
+ living_parent.throw_alert("radioactive_area", /atom/movable/screen/alert/radioactive_area)
+
+/// Try and irradiate them. If we chance fail, we come back harder
+/datum/component/radioactive_exposure/proc/attempt_irradiate()
+ if(!SSradiation.wearing_rad_protected_clothing(parent) && SSradiation.can_irradiate_basic(parent))
+ if(prob(irradiation_chance))
+ SSradiation.irradiate(parent)
+ var/atom/atom = parent
+ atom.investigate_log("was irradiated by [source].", INVESTIGATE_RADIATION)
+ else
+ irradiation_chance += irradiation_chance_increment
+ else // we're immune, either through species, clothing, already being irradiated, etcetera
+ // we slowly decrease the prob chance untill we hit the base probability again
+ irradiation_chance = max(irradiation_chance - irradiation_chance_increment, irradiation_chance_base)
+
+ // Even if they are immune, or got irradiated plan a new check in-case they lose their protection or irradiation
+ addtimer(CALLBACK(src, PROC_REF(attempt_irradiate)), irradiation_interval)
+
+/datum/component/radioactive_exposure/proc/on_exited(atom/movable/also_parent, area/old_area, direction)
+ SIGNAL_HANDLER
+
+ if(istype(get_area(parent), radioactive_areas)) //we left to another area that is also radioactive, so dont do anything
+ return
+
+ qdel(src)
+
+/datum/component/radioactive_exposure/Destroy(force)
+ var/mob/living/carbon/human/human_parent = parent
+ human_parent.clear_alert("radioactive_area")
+
+ return ..()
+
+/atom/movable/screen/alert/radioactive_area
+ name = "Radioactive Area"
+ desc = "This place is no good! We need to get some protection or get out fast!"
+// use_user_hud_icon = TRUE
+ icon_state = "radioactive_area"
diff --git a/code/datums/components/traits/radiation_effects.dm b/code/datums/components/traits/radiation_effects.dm
index 095cd2834d..87bc413dc0 100644
--- a/code/datums/components/traits/radiation_effects.dm
+++ b/code/datums/components/traits/radiation_effects.dm
@@ -69,11 +69,14 @@
///How much the damage we take from rads is multiplied by.
var/damage_multiplier = 1.0
+ ///If we use a toony glow instead of a more emmissive one.
+ var/toony = FALSE
+
dupe_mode = COMPONENT_DUPE_UNIQUE
dupe_type = /datum/component/radiation_effects
-/datum/component/radiation_effects/Initialize(glows, radiation_glow_minor_threshold, contamination, contamination_strength, radiation_color, intensity_mod, range_mod, radiation_immunity, radiation_healing, radiation_dissipation, radiation_nutrition, radiation_nutrition_cap, glow_toggle, nutrition_toggle)
+/datum/component/radiation_effects/Initialize(glows, radiation_glow_minor_threshold, contamination, contamination_strength, radiation_color, intensity_mod, range_mod, radiation_immunity, radiation_healing, radiation_dissipation, radiation_nutrition, radiation_nutrition_cap, glow_toggle, nutrition_toggle, toony)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
@@ -115,16 +118,25 @@
if(show_panel)
add_verb(parent, /mob/living/proc/radiation_control_panel)
+ if(toony)
+ src.toony = toony
+
/datum/component/radiation_effects/Destroy(force)
+ var/atom/movable/parent_movable = parent
if(show_panel)
remove_verb(parent, /mob/living/proc/radiation_control_panel)
+ if(istype(parent_movable))//For the toony glow.
+ var/filter = parent_movable.get_filter("rad_glow")
+ if(filter)
+ parent_movable.remove_filter("rad_glow")
return ..()
/datum/component/radiation_effects/RegisterWithParent()
RegisterSignal(parent, COMSIG_HANDLE_RADIATION, PROC_REF(process_component))
RegisterSignal(parent, COMSIG_LIVING_LIFE, PROC_REF(process_glow))
RegisterSignal(parent, COMSIG_LIVING_IRRADIATE_EFFECT, PROC_REF(handle_irradiate_effect))
+ RegisterSignal(parent, COMSIG_GEIGER_COUNTER_SCAN, PROC_REF(on_geiger_counter_scan))
/datum/component/radiation_effects/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_HANDLE_RADIATION, COMSIG_LIVING_LIFE, COMSIG_LIVING_IRRADIATE_EFFECT))
@@ -136,10 +148,12 @@
if(living_guy.glow_override) //Toggled glow off while we were still actively glowing.
living_guy.glow_override = FALSE
living_guy.set_light(0)
+ living_guy.remove_filter("rad_glow")
return
if(living_guy.radiation < radiation_glow_threshold)
living_guy.glow_override = FALSE
living_guy.set_light(0)
+ living_guy.remove_filter("rad_glow")
return
if(glows)
@@ -148,12 +162,16 @@
living_guy.set_light(l_range = light_range, l_power = light_power, l_color = radiation_color, l_on = TRUE)
living_guy.glow_override = TRUE
+ if(toony)
+ var/filter = living_guy.get_filter("rad_glow")
+ if(!filter)
+ create_toony_glow()
///Handles the radiation removal, immunity, and healing effects.
/datum/component/radiation_effects/proc/process_component()
SIGNAL_HANDLER
var/mob/living/living_guy = parent
- if(living_guy.radiation > RADIATION_CAP)
+ if((living_guy.radiation > RADIATION_CAP || living_guy.radiation < 0) || (living_guy.accumulated_rads > RADIATION_CAP || living_guy.accumulated_rads < 0))
living_guy.radiation = CLAMP(living_guy.radiation,0,RADIATION_CAP)
living_guy.accumulated_rads = CLAMP(living_guy.accumulated_rads,0,RADIATION_CAP)
@@ -175,7 +193,15 @@
//End of the calculation.
if(contamination && living_guy.radiation > contamination_threshold)
- SSradiation.radiate(living_guy, rads * contamination_strength * rad_removal_mod)
+ //SSradiation.radiate(living_guy, rads * contamination_strength * rad_removal_mod)
+ radiation_pulse(
+ living_guy,
+ max_range = 2,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = CLAMP(rads * contamination_strength, 0, 25),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rads * contamination_strength
+ )
///Used for radiation nutrition and healing.
var/rads_to_utilize
@@ -296,6 +322,31 @@
to_chat(parent, span_info("You are [radiation_nutrition ? "now" : "no longer"] gaining nutrition from radiation."))
return FALSE
+/datum/component/radiation_effects/proc/create_toony_glow()
+ var/atom/movable/parent_movable = parent
+ if (!istype(parent_movable))
+ return
+
+ parent_movable.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#39ff1430", "size" = 2))
+ addtimer(CALLBACK(src, PROC_REF(toony_glow_loop), parent_movable), rand(0.1 SECONDS, 1.9 SECONDS)) // Things should look uneven
+
+/datum/component/radiation_effects/proc/toony_glow_loop(atom/movable/parent_movable)
+ var/filter = parent_movable.get_filter("rad_glow")
+ if (!filter)
+ return
+
+ animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1)
+ animate(alpha = 40, time = 2.5 SECONDS)
+
+
+/datum/component/radiation_effects/proc/on_geiger_counter_scan(mob/living/living_source, mob/user, obj/item/geiger/geiger_counter)
+ SIGNAL_HANDLER
+ if(living_source.radiation > 0)
+ if(contamination && living_source.radiation > contamination_threshold) //Are we spreading radiation?
+ to_chat(user, span_bolddanger("[icon2html(geiger_counter, user)] Subject is irradiated and offputting radiation."))
+ else
+ to_chat(user, span_bolddanger("[icon2html(geiger_counter, user)] Subject is irradiated."))
+
/mob/living/proc/get_radiation_component()
var/datum/component/radiation_effects/rad = GetComponent(/datum/component/radiation_effects)
if(rad)
diff --git a/code/datums/elements/radiation_protected_clothing.dm b/code/datums/elements/radiation_protected_clothing.dm
new file mode 100644
index 0000000000..6d6d98c14b
--- /dev/null
+++ b/code/datums/elements/radiation_protected_clothing.dm
@@ -0,0 +1,24 @@
+/// Marks the item as being radiation protected.
+/// Adds the TRAIT_RADIATION_PROTECTED_CLOTHING trait, as well as adding an
+/// extra bit to the examine descrpition.
+/datum/element/radiation_protected_clothing
+
+/datum/element/radiation_protected_clothing/Attach(datum/target)
+ . = ..()
+
+ if (!isclothing(target))
+ return ELEMENT_INCOMPATIBLE
+
+ ADD_TRAIT(target, TRAIT_RADIATION_PROTECTED_CLOTHING, REF(src))
+ RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+
+/datum/element/radiation_protected_clothing/Detach(datum/source, ...)
+ REMOVE_TRAIT(source, TRAIT_RADIATION_PROTECTED_CLOTHING, REF(src))
+ UnregisterSignal(source, COMSIG_ATOM_EXAMINE)
+
+ return ..()
+
+/datum/element/radiation_protected_clothing/proc/on_examine(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER
+
+ examine_text += span_notice("A patch with a hazmat sign on the side suggests it would protect you from radiation.")
diff --git a/code/datums/looping_sounds/item_sounds.dm b/code/datums/looping_sounds/item_sounds.dm
index 8a79bdf770..8f82ab0ef4 100644
--- a/code/datums/looping_sounds/item_sounds.dm
+++ b/code/datums/looping_sounds/item_sounds.dm
@@ -1,33 +1,3 @@
-/datum/looping_sound/geiger
- mid_sounds = list(
- list('sound/items/geiger/low1.ogg'=1, 'sound/items/geiger/low2.ogg'=1, 'sound/items/geiger/low3.ogg'=1, 'sound/items/geiger/low4.ogg'=1),
- list('sound/items/geiger/med1.ogg'=1, 'sound/items/geiger/med2.ogg'=1, 'sound/items/geiger/med3.ogg'=1, 'sound/items/geiger/med4.ogg'=1),
- list('sound/items/geiger/high1.ogg'=1, 'sound/items/geiger/high2.ogg'=1, 'sound/items/geiger/high3.ogg'=1, 'sound/items/geiger/high4.ogg'=1),
- list('sound/items/geiger/ext1.ogg'=1, 'sound/items/geiger/ext2.ogg'=1, 'sound/items/geiger/ext3.ogg'=1, 'sound/items/geiger/ext4.ogg'=1)
- )
- mid_length = 1 SECOND
- volume = 25
- var/last_radiation
-
-/datum/looping_sound/geiger/get_sound(starttime)
- var/danger
- switch(last_radiation)
- if(0 to RAD_LEVEL_MODERATE)
- danger = 1
- if(RAD_LEVEL_MODERATE to RAD_LEVEL_HIGH)
- danger = 2
- if(RAD_LEVEL_HIGH to RAD_LEVEL_VERY_HIGH)
- danger = 3
- if(RAD_LEVEL_VERY_HIGH to INFINITY)
- danger = 4
- else
- return null
- return ..(starttime, mid_sounds[danger])
-
-/datum/looping_sound/geiger/stop()
- . = ..()
- last_radiation = 0
-
/datum/looping_sound/small_motor
start_sound = 'sound/items/small_motor/motor_start_nopull.ogg'
start_length = 2 SECONDS
diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm
index d109605e4e..1a55e54237 100644
--- a/code/game/atom/_atom.dm
+++ b/code/game/atom/_atom.dm
@@ -60,6 +60,9 @@
/// You will need to manage adding/removing from this yourself, but I'll do the updating for you
var/list/image/update_on_z
+ /// Radiation insulation types
+ var/rad_insulation = RAD_NO_INSULATION
+
/atom/Destroy()
if(reagents)
QDEL_NULL(reagents)
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 90ce8a7a43..d55a0be0e7 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -310,13 +310,11 @@ GLOBAL_LIST_INIT(meteors_catastrophic, list(
meteordrop = /obj/item/ore/uranium
wall_power = 75
-
/obj/effect/meteor/irradiated/meteor_effect(var/explode)
..()
if(explode)
explosion(src.loc, 0, 0, 4, 3, 0)
new /obj/effect/decal/cleanable/greenglow(get_turf(src))
- SSradiation.radiate(src, 50)
// This meteor fries toasters.
/obj/effect/meteor/emp
diff --git a/code/game/machinery/atm_ret_field.dm b/code/game/machinery/atm_ret_field.dm
index fb5805cfa3..0084c4e2eb 100644
--- a/code/game/machinery/atm_ret_field.dm
+++ b/code/game/machinery/atm_ret_field.dm
@@ -197,6 +197,7 @@
light_power = 1
light_color = "#FFFFFF"
light_on = TRUE
+ rad_insulation = RAD_LIGHT_INSULATION
/obj/structure/atmospheric_retention_field/update_icon()
cut_overlays() //overlays.Cut()
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index bc57316cbc..84529f5443 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -269,6 +269,7 @@
if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70) //Patch by Aranclanos to stop people from taking burn damage after being ejected
occupant.bodytemperature = 261 // Changed to 70 from 140 by Zuhayr due to reoccurance of bug.
unbuckle_mob(occupant, force = TRUE)
+ //REMOVE_TRAIT(occupant, TRAIT_STASIS, REF(src)) //Stops life almost entirely, so not done here.
occupant = null
current_heat_capacity = initial(current_heat_capacity)
update_use_power(USE_POWER_IDLE)
@@ -297,6 +298,7 @@
if(M.health > -100 && (M.health < 0 || M.sleeping))
to_chat(M, span_boldnotice("You feel a cold liquid surround you. Your skin starts to freeze up."))
occupant = M
+ //ADD_TRAIT(occupant, TRAIT_STASIS, REF(src)) //Stops life almost entirely, so not done here.
buckle_mob(occupant, forced = TRUE, check_loc = FALSE)
vis_contents |= occupant
occupant.pixel_y += 19
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index ac036b5d26..8f22470dae 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -61,6 +61,7 @@
var/knock_hammer_sound = 'sound/weapons/sonic_jackhammer.ogg'
var/knock_unpowered_sound = 'sound/machines/door/knock_glass.ogg'
var/mob/hold_open
+ rad_insulation = RAD_MEDIUM_INSULATION
// Frozen airlocks and how to deice them
var/frozen = FALSE
diff --git a/code/game/machinery/doors/airlock_subtypes.dm b/code/game/machinery/doors/airlock_subtypes.dm
index 8fd8eef399..41b922ed13 100644
--- a/code/game/machinery/doors/airlock_subtypes.dm
+++ b/code/game/machinery/doors/airlock_subtypes.dm
@@ -381,14 +381,26 @@
icon = 'icons/obj/doors/Dooruranium.dmi'
mineral = MAT_URANIUM
var/last_event = 0
- var/rad_power = 7.5
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
-/obj/machinery/door/airlock/uranium/process()
- if(world.time > last_event+20)
- if(prob(50))
- SSradiation.radiate(src, rad_power)
- last_event = world.time
- ..()
+/obj/machinery/door/airlock/uranium/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 5,
+ )
+ last_event = world.time
+ active = FALSE
/obj/machinery/door/airlock/uranium_appearance
icon = 'icons/obj/doors/Dooruranium.dmi'
diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm
index 8f04573507..86c7a691aa 100644
--- a/code/game/machinery/doors/blast_door.dm
+++ b/code/game/machinery/doors/blast_door.dm
@@ -64,7 +64,6 @@
icon_state = icon_state_closed
else
icon_state = icon_state_open
- SSradiation.resistance_cache.Remove(get_turf(src))
return
// Proc: emag_act()
@@ -90,6 +89,7 @@
update_nearby_tiles()
update_icon()
set_opacity(0)
+ rad_insulation = RAD_NO_INSULATION
addtimer(CALLBACK(src, PROC_REF(complete_force_open)), 1.5 SECONDS, TIMER_DELETE_ME|TIMER_UNIQUE)
/obj/machinery/door/blast/proc/complete_force_open()
@@ -112,6 +112,7 @@
density = TRUE
update_nearby_tiles()
update_icon()
+ rad_insulation = RAD_EXTREME_INSULATION
if(istransparent)
set_opacity(0)
else
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index e3176880fc..2f1b45ae17 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -448,7 +448,6 @@
icon_state = "door1"
else
icon_state = "door0"
- SSradiation.resistance_cache.Remove(get_turf(src))
return
diff --git a/code/game/mecha/combat/phazon.dm b/code/game/mecha/combat/phazon.dm
index 4b70f0d2fd..011486211d 100644
--- a/code/game/mecha/combat/phazon.dm
+++ b/code/game/mecha/combat/phazon.dm
@@ -114,9 +114,16 @@
..()
if(phasing)
phasing = FALSE
- SSradiation.radiate(get_turf(src), 30)
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE * 5,
+ strength = 250
+ )
log_append_to_last("WARNING: BLUESPACE DRIVE INSTABILITY DETECTED. DISABLING DRIVE.",1)
visible_message(span_alien("The [src.name] appears to flicker, before its silhouette stabilizes!"))
+
return
/obj/mecha/combat/phazon/janus/dynbulletdamage(var/obj/item/projectile/Proj)
diff --git a/code/game/mecha/equipment/tools/generator.dm b/code/game/mecha/equipment/tools/generator.dm
index e373b9a25b..3b8323984c 100644
--- a/code/game/mecha/equipment/tools/generator.dm
+++ b/code/game/mecha/equipment/tools/generator.dm
@@ -138,7 +138,13 @@
/obj/item/mecha_parts/mecha_equipment/generator/nuclear/process()
if(..())
- SSradiation.radiate(src, (rad_per_cycle * 3))
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 25
+ )
return
/obj/item/mecha_parts/mecha_equipment/generator/nuclear/critfail()
diff --git a/code/game/objects/effects/decals/Cleanable/misc.dm b/code/game/objects/effects/decals/Cleanable/misc.dm
index 7db48b0935..f74736170c 100644
--- a/code/game/objects/effects/decals/Cleanable/misc.dm
+++ b/code/game/objects/effects/decals/Cleanable/misc.dm
@@ -23,10 +23,40 @@
qdel(src)
/obj/effect/decal/cleanable/greenglow
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
/obj/effect/decal/cleanable/greenglow/Initialize(mapload, _age)
. = ..()
QDEL_IN(src, 2 MINUTES)
+ START_PROCESSING(SSobj, src)
+
+/obj/effect/decal/cleanable/greenglow/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ . = ..()
+
+/obj/effect/decal/cleanable/greenglow/process()
+ radiate()
+ ..()
+
+/obj/effect/decal/cleanable/greenglow/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 2
+ )
+ last_event = world.time
+ active = FALSE
+
/obj/effect/decal/cleanable/dirt
name = "dirt"
diff --git a/code/game/objects/effects/map_effects/radiation_emitter.dm b/code/game/objects/effects/map_effects/radiation_emitter.dm
index 510a0eae1c..accada19cb 100644
--- a/code/game/objects/effects/map_effects/radiation_emitter.dm
+++ b/code/game/objects/effects/map_effects/radiation_emitter.dm
@@ -2,7 +2,35 @@
/obj/effect/map_effect/radiation_emitter
name = "radiation emitter"
icon_state = "radiation_emitter"
+ var/range = 3
var/radiation_power = 30 // Bigger numbers means more radiation.
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
+ var/strength = 50
+
+/obj/effect/map_effect/radiation_emitter/process()
+ radiate()
+ ..()
+
+/obj/effect/map_effect/radiation_emitter/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = range,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = radiation_power,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = strength
+ )
+ last_event = world.time
+ active = FALSE
+
/obj/effect/map_effect/radiation_emitter/Initialize(mapload)
START_PROCESSING(SSobj, src)
@@ -12,8 +40,7 @@
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/effect/map_effect/radiation_emitter/process()
- SSradiation.radiate(src, radiation_power)
-
/obj/effect/map_effect/radiation_emitter/strong
+ range = 7
radiation_power = 100
+ strength = 250
diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm
index 7c4a8648f1..51c0034c00 100644
--- a/code/game/objects/items/bodybag.dm
+++ b/code/game/objects/items/bodybag.dm
@@ -195,6 +195,7 @@
syringe = null
/obj/structure/closet/body_bag/cryobag/Entered(atom/movable/AM)
+ ADD_TRAIT(AM, TRAIT_STASIS, REF(src))
if(ishuman(AM))
var/mob/living/carbon/human/H = AM
H.Stasis(stasis_level)
@@ -209,6 +210,7 @@
..()
/obj/structure/closet/body_bag/cryobag/Exited(atom/movable/AM)
+ REMOVE_TRAIT(AM, TRAIT_STASIS, REF(src))
if(ishuman(AM))
var/mob/living/carbon/human/H = AM
H.Stasis(0)
diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm
index 6d72f30a61..308743e7b2 100644
--- a/code/game/objects/items/devices/defib.dm
+++ b/code/game/objects/items/devices/defib.dm
@@ -575,6 +575,9 @@
/obj/item/shockpaddles/standalone
desc = "A pair of shockpaddles powered by an experimental miniaturized reactor" //Inspired by the advanced e-gun
var/fail_counter = 0
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
/obj/item/shockpaddles/standalone/Destroy()
. = ..()
@@ -585,12 +588,25 @@
return 1
/obj/item/shockpaddles/standalone/checked_use(var/charge_amt)
- SSradiation.radiate(src, charge_amt/12) //just a little bit of radiation. It's the price you pay for being powered by magic I guess
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 50
+ )
return 1
/obj/item/shockpaddles/standalone/process()
if(fail_counter > 0)
- SSradiation.radiate(src, fail_counter--)
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 15
+ )
+ fail_counter--
else
STOP_PROCESSING(SSobj, src)
diff --git a/code/game/objects/items/devices/geiger.dm b/code/game/objects/items/devices/geiger.dm
index f300cf1f39..3c667f61b6 100644
--- a/code/game/objects/items/devices/geiger.dm
+++ b/code/game/objects/items/devices/geiger.dm
@@ -1,102 +1,136 @@
-//Geiger counter
-//Rewritten version of TG's geiger counter
-//I opted to show exact radiation levels
-
-/obj/item/geiger
- name = "geiger counter"
- desc = "A handheld device used for detecting and measuring radiation in an area."
- icon = 'icons/obj/device.dmi'
+/obj/item/geiger //DISCLAIMER: I know nothing about how real-life Geiger counters work. This will not be realistic. ~Xhuis
+ name = "\improper Geiger counter"
+ desc = "A handheld device used for detecting and measuring radiation pulses."
+ icon = 'icons/obj/devices/scanner.dmi'
icon_state = "geiger_off"
item_state = "multitool"
+ item_icons = list(
+ slot_l_hand_str = 'icons/mob/inhands/equipment/tools_lefthand.dmi',
+ slot_r_hand_str = 'icons/mob/inhands/equipment/tools_righthand.dmi',
+ )
w_class = ITEMSIZE_SMALL
- var/scanning = 0
- var/radiation_count = 0
- var/datum/looping_sound/geiger/soundloop
+ slot_flags = SLOT_BELT
+ item_flags = NOBLUDGEON
+ matter = list(/datum/material/steel = SHEET_MATERIAL_AMOUNT * 1.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 1.5)
+
+ var/last_perceived_radiation_danger = null
+
+ var/scanning = FALSE
+
var/mounted = FALSE
- pickup_sound = 'sound/items/pickup/device.ogg'
- drop_sound = 'sound/items/drop/device.ogg'
-
/obj/item/geiger/Initialize(mapload)
- soundloop = new(list(src), FALSE)
- return ..()
+ . = ..()
-/obj/item/geiger/Destroy()
- STOP_PROCESSING(SSobj, src)
- QDEL_NULL(soundloop)
- return ..()
-
-/obj/item/geiger/process()
- get_radiation()
-
-/obj/item/geiger/proc/get_radiation()
- if(!scanning)
- return
- radiation_count = SSradiation.get_rads_at_turf(get_turf(src))
- update_icon()
- update_sound()
+ RegisterSignal(src, COMSIG_IN_RANGE_OF_IRRADIATION, PROC_REF(on_pre_potential_irradiation))
/obj/item/geiger/examine(mob/user)
. = ..()
- get_radiation()
- . += span_warning("[scanning ? "Ambient" : "Stored"] radiation level: [radiation_count ? radiation_count : "0"]Bq.")
-
-/obj/item/geiger/rad_act(amount)
- if(!amount || !scanning)
- return FALSE
-
- if(amount > radiation_count)
- radiation_count = amount
-
- update_icon()
- update_sound()
-
-/obj/item/geiger/proc/update_sound()
- var/datum/looping_sound/geiger/loop = soundloop
if(!scanning)
- loop.stop()
return
- if(!radiation_count)
- loop.stop()
- return
- loop.last_radiation = radiation_count
- loop.start()
-
-/obj/item/geiger/attack_self(mob/user)
- . = ..(user)
- if(.)
- return TRUE
- if(mounted)
- return
- scanning = !scanning
- if(scanning)
- START_PROCESSING(SSobj, src)
- else
- STOP_PROCESSING(SSobj, src)
- update_icon()
- update_sound()
- to_chat(user, span_notice("[icon2html(src, user.client)] You switch [scanning ? "on" : "off"] \the [src]."))
+ . += span_info("Alt-click it to clear stored radiation levels.")
+ switch(last_perceived_radiation_danger)
+ if(null)
+ . += span_notice("Ambient radiation level count reports that all is well.")
+ if(PERCEIVED_RADIATION_DANGER_LOW)
+ . += span_alert("Ambient radiation levels slightly above average.")
+ if(PERCEIVED_RADIATION_DANGER_MEDIUM)
+ . += span_warning("Ambient radiation levels above average.")
+ if(PERCEIVED_RADIATION_DANGER_HIGH)
+ . += span_danger("Ambient radiation levels highly above average.")
+ if(PERCEIVED_RADIATION_DANGER_EXTREME)
+ . += span_suicide("Ambient radiation levels reaching critical level!")
/obj/item/geiger/update_icon()
if(!scanning)
icon_state = "geiger_off"
- return 1
+ return ..()
- switch(radiation_count)
+ switch(last_perceived_radiation_danger)
if(null)
icon_state = "geiger_on_1"
- if(-INFINITY to RAD_LEVEL_LOW)
- icon_state = "geiger_on_1"
- if(RAD_LEVEL_LOW to RAD_LEVEL_MODERATE)
+ if(PERCEIVED_RADIATION_DANGER_LOW)
icon_state = "geiger_on_2"
- if(RAD_LEVEL_MODERATE to RAD_LEVEL_HIGH)
+ if(PERCEIVED_RADIATION_DANGER_MEDIUM)
icon_state = "geiger_on_3"
- if(RAD_LEVEL_HIGH to RAD_LEVEL_VERY_HIGH)
+ if(PERCEIVED_RADIATION_DANGER_HIGH)
icon_state = "geiger_on_4"
- if(RAD_LEVEL_VERY_HIGH to INFINITY)
+ if(PERCEIVED_RADIATION_DANGER_EXTREME)
icon_state = "geiger_on_5"
+ return ..()
-///////////////////////
+/obj/item/geiger/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ scanning = !scanning
+
+ if (scanning)
+ AddComponent(/datum/component/geiger_sound)
+ else
+ qdel(GetComponent(/datum/component/geiger_sound))
+
+ update_icon()
+ balloon_alert(user, "switch [scanning ? "on" : "off"]")
+
+/obj/item/geiger/afterattack(atom/interacting_with, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
+ return NONE
+ return attack_at_range(interacting_with, user, proximity_flag, click_parameters)
+
+/obj/item/geiger/proc/attack_at_range(atom/interacting_with, mob/living/user, proximity_flag, click_parameters) //This is ranged_interact_with_atom on TG but we don't have that yet.
+ if(!CAN_IRRADIATE(interacting_with))
+ return NONE
+
+ user.visible_message(span_notice("[user] scans [interacting_with] with [src]."), span_notice("You scan [interacting_with]'s radiation levels with [src]..."))
+ addtimer(CALLBACK(src, PROC_REF(scan), interacting_with, user), 20, TIMER_UNIQUE) // Let's not have spamming GetAllContents
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/geiger/equipped(mob/user, slot, initial)
+ . = ..()
+
+ RegisterSignal(user, COMSIG_IN_RANGE_OF_IRRADIATION, PROC_REF(on_pre_potential_irradiation))
+
+/obj/item/geiger/dropped(mob/user, silent = FALSE)
+ . = ..()
+
+ UnregisterSignal(user, COMSIG_IN_RANGE_OF_IRRADIATION)
+
+/obj/item/geiger/proc/on_pre_potential_irradiation(datum/source, datum/radiation_pulse_information/pulse_information, insulation_to_target)
+ SIGNAL_HANDLER
+
+ last_perceived_radiation_danger = get_perceived_radiation_danger(pulse_information, insulation_to_target)
+ addtimer(CALLBACK(src, PROC_REF(reset_perceived_danger)), TIME_WITHOUT_RADIATION_BEFORE_RESET, TIMER_UNIQUE | TIMER_OVERRIDE)
+
+ if (scanning)
+ update_icon()
+
+/obj/item/geiger/proc/reset_perceived_danger()
+ last_perceived_radiation_danger = null
+ if (scanning)
+ update_icon()
+
+/obj/item/geiger/proc/scan(atom/target, mob/user)
+ if (SEND_SIGNAL(target, COMSIG_GEIGER_COUNTER_SCAN, user, src) & COMSIG_GEIGER_COUNTER_SCAN_SUCCESSFUL)
+ return
+
+ if(isliving(target))
+ var/mob/living/living_target = target
+ if(living_target.radiation)
+ to_chat(user, span_notice("[icon2html(src, user)] [living_target] is reporting a radiation level of [living_target.radiation]."))
+ return
+
+ to_chat(user, span_notice("[icon2html(src, user)] [isliving(target) ? "Subject" : "Target"] is free of radioactive contamination."))
+
+/obj/item/geiger/click_alt(mob/living/user)
+ if(!scanning)
+ to_chat(user, span_warning("[src] must be on to reset its radiation level!"))
+ return CLICK_ACTION_BLOCKING
+ to_chat(user, span_notice("You flush [src]'s radiation counts, resetting it to normal."))
+ last_perceived_radiation_danger = null
+ update_icon()
+ return CLICK_ACTION_SUCCESS
/obj/item/geiger/wall
name = "mounted geiger counter"
@@ -105,8 +139,7 @@
icon_state = "geiger_wall"
item_state = "geiger_wall"
anchored = TRUE
- scanning = 1
- radiation_count = 0
+ scanning = TRUE
plane = TURF_PLANE
layer = ABOVE_TURF_LAYER
w_class = ITEMSIZE_LARGE
@@ -118,41 +151,25 @@
mounted = TRUE
/obj/item/geiger/wall/Initialize(mapload)
- START_PROCESSING(SSobj, src)
- soundloop = new(list(src), FALSE)
- return ..()
+ . = ..()
+ if(scanning)
+ AddComponent(/datum/component/geiger_sound)
-/obj/item/geiger/wall/Destroy()
- STOP_PROCESSING(SSobj, src)
- QDEL_NULL(soundloop)
- return ..()
-
-/obj/item/geiger/wall/attack_self(mob/user)
- . = ..(user)
- if(.)
- return TRUE
- scanning = !scanning
- update_icon()
- update_sound()
- to_chat(user, span_notice("[icon2html(src, user.client)] You switch [scanning ? "on" : "off"] \the [src]."))
/obj/item/geiger/wall/update_icon()
if(!scanning)
icon_state = "geiger_wall-p"
return 1
-
- switch(radiation_count)
+ switch(last_perceived_radiation_danger)
if(null)
icon_state = "geiger_level_1"
- if(-INFINITY to RAD_LEVEL_LOW)
- icon_state = "geiger_level_1"
- if(RAD_LEVEL_LOW to RAD_LEVEL_MODERATE)
+ if(PERCEIVED_RADIATION_DANGER_LOW)
icon_state = "geiger_level_2"
- if(RAD_LEVEL_MODERATE to RAD_LEVEL_HIGH)
+ if(PERCEIVED_RADIATION_DANGER_MEDIUM)
icon_state = "geiger_level_3"
- if(RAD_LEVEL_HIGH to RAD_LEVEL_VERY_HIGH)
+ if(PERCEIVED_RADIATION_DANGER_HIGH)
icon_state = "geiger_level_4"
- if(RAD_LEVEL_VERY_HIGH to INFINITY)
+ if(PERCEIVED_RADIATION_DANGER_EXTREME)
icon_state = "geiger_level_5"
/obj/item/geiger/wall/attack_ai(mob/user as mob)
diff --git a/code/game/objects/items/poi_items.dm b/code/game/objects/items/poi_items.dm
index 7e1a2841ce..744050607f 100644
--- a/code/game/objects/items/poi_items.dm
+++ b/code/game/objects/items/poi_items.dm
@@ -27,18 +27,58 @@
name = "misshapen manhole cover"
desc = "The top of this twisted chunk of metal is faintly stamped with a five pointed star. 'Property of US Army, Pascal B - 1957'."
catalogue_data = list(/datum/category_item/catalogue/information/objects/pascalb)
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
/obj/item/poi/pascalb/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
/obj/item/poi/pascalb/process()
- SSradiation.radiate(src, 5)
+ radiate()
+ ..()
+
+/obj/item/poi/pascalb/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 25
+ )
+ last_event = world.time
+ active = FALSE
/obj/item/poi/pascalb/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
+/obj/item/poi/pascalb/deadly //For testing purposes, mainly.
+
+/obj/item/poi/pascalb/deadly/radiate()
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = 100,
+ strength = 250
+ )
+ last_event = world.time
+ active = FALSE
+
/datum/category_item/catalogue/information/objects/oldreactor
name = "Object - Zorren Fission Reactor Rack"
desc = "Prior to Humanity's arrival in the virgo-erigone space, introducing advanced phoron technologies, \
@@ -78,15 +118,38 @@
name = "ruptured fission reactor rack"
desc = "This broken hunk of machinery looks extremely dangerous."
catalogue_data = list(/datum/category_item/catalogue/information/objects/oldreactor)
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
/obj/item/poi/brokenoldreactor/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
/obj/item/poi/brokenoldreactor/process()
- SSradiation.radiate(src, 25)
+ radiate()
+ ..()
+
+/obj/item/poi/brokenoldreactor/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 50
+ )
+ last_event = world.time
+ active = FALSE
/obj/item/poi/brokenoldreactor/Destroy()
+ UnregisterSignal(src, COMSIG_ATOM_PROPAGATE_RAD_PULSE)
STOP_PROCESSING(SSobj, src)
return ..()
diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm
index 49440bfae5..5d07d4d4ce 100644
--- a/code/game/objects/items/weapons/cigs_lighters.dm
+++ b/code/game/objects/items/weapons/cigs_lighters.dm
@@ -1100,9 +1100,13 @@ CIGARETTE PACKETS ARE IN FANCY.DM
user.drop_from_inventory(e)
log_and_message_admins("[user] dusted themselves and caused massive radiation with [src]!",user)
user.dust()
- var/rads = 500
- SSradiation.radiate(src, rads)
-
+ radiation_pulse(
+ src,
+ max_range = 12,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE * 2,
+ strength = 300
+ )
set_light(5)
START_PROCESSING(SSobj, src)
else
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index 333470e8d7..a041e86031 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -18,6 +18,7 @@
var/upgrading = FALSE
var/applies_material_colour = 1
var/wall_type = /turf/simulated/wall
+ rad_insulation = RAD_VERY_LIGHT_INSULATION
/obj/structure/girder/Initialize(mapload, var/material_key)
. = ..()
@@ -42,9 +43,16 @@
/obj/structure/girder/proc/radiate()
var/total_radiation = girder_material.radioactivity + (reinf_material ? reinf_material.radioactivity / 2 : 0)
if(!total_radiation)
- return
+ return FALSE
- SSradiation.radiate(src, total_radiation)
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = total_radiation
+ )
return total_radiation
diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm
index 8bfbf1e656..a88157d39a 100644
--- a/code/game/objects/structures/holosign.dm
+++ b/code/game/objects/structures/holosign.dm
@@ -59,6 +59,7 @@
density = FALSE
layer = ABOVE_TURF_LAYER
can_atmos_pass = ATMOS_PASS_NO
+ rad_insulation = RAD_LIGHT_INSULATION
alpha = 150
/obj/structure/holosign/barrier/combifan/Destroy()
@@ -75,6 +76,7 @@
icon_state = "holo_medical"
alpha = 125
var/buzzed = 0
+ rad_insulation = RAD_NO_INSULATION
/obj/structure/holosign/barrier/medical/CanPass(atom/movable/mover, border_dir)
. = ..()
diff --git a/code/game/objects/structures/simple_doors.dm b/code/game/objects/structures/simple_doors.dm
index 9b93ea748a..f1564af6fd 100644
--- a/code/game/objects/structures/simple_doors.dm
+++ b/code/game/objects/structures/simple_doors.dm
@@ -23,6 +23,7 @@
var/can_pick = TRUE //can it be picked/bypassed?
var/lock_difficulty = 1 //multiplier to picking/bypassing time
var/keysound = 'sound/items/toolbelt_equip.ogg'
+ rad_insulation = RAD_MEDIUM_INSULATION
/obj/structure/simple_door/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
TemperatureAct(exposed_temperature)
@@ -240,19 +241,57 @@
/obj/structure/simple_door/process()
if(!material.radioactivity)
return
- SSradiation.radiate(src, round(material.radioactivity/3))
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = round((material.radioactivity * 0.33), 0.1),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = material.radioactivity
+ )
/obj/structure/simple_door/iron/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_IRON)
+/obj/structure/simple_door/silver
+ rad_insulation = RAD_HEAVY_INSULATION
+
/obj/structure/simple_door/silver/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_SILVER)
+/obj/structure/simple_door/gold
+ rad_insulation = RAD_HEAVY_INSULATION
+
/obj/structure/simple_door/gold/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_GOLD)
+/obj/structure/simple_door/uranium
+ rad_insulation = RAD_NO_INSULATION
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
+
/obj/structure/simple_door/uranium/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_URANIUM)
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/simple_door/uranium/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 5
+ )
+ last_event = world.time
+ active = FALSE
/obj/structure/simple_door/sandstone/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_SANDSTONE)
@@ -260,9 +299,13 @@
/obj/structure/simple_door/phoron/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_PHORON)
+/obj/structure/simple_door/diamond
+ rad_insulation = RAD_EXTREME_INSULATION
+
/obj/structure/simple_door/diamond/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_DIAMOND)
+//I was going to give wooden doors RAD_VERY_LIGHT_INSULATION but they need a proper parent instead of this garbage.
/obj/structure/simple_door/wood/Initialize(mapload,var/material_name)
. = ..(mapload, material_name || MAT_WOOD)
knock_sound = 'sound/machines/door/knock_wood.wav'
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 45ec2b934d..ea7d9f5150 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -261,10 +261,8 @@
//Yes, showers are super powerful as far as washing goes.
/obj/machinery/shower/proc/wash_atom(atom/A)
- /* //TG cleans rads and only does CLEAN_WASH, not sure if that's wanted here.
A.wash(CLEAN_RAD | CLEAN_TYPE_WEAK) // Clean radiation non-instantly
A.wash(CLEAN_WASH)
- */
A.wash(CLEAN_SCRUB)
reagents.splash(A, reaction_volume / 20, 1, TRUE, min_spill = 0, max_spill = 0) //Reaction volume needs to be divided by 20 due to a larger internal volume
@@ -274,6 +272,7 @@
check_heat(L)
L.extinguish_mob()
L.adjust_fire_stacks(-20) //Douse ourselves with water to avoid fire more easily
+ L.radiation = CLAMP(L.radiation - 5, 0, RADIATION_CAP)
if(!iscarbon(A))
return
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index cf376d6dfb..530bb712bd 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -23,6 +23,7 @@
var/glasstype = null // Set this in subtypes. Null is assumed strange or otherwise impossible to dismantle, such as for shuttle glass.
var/silicate = 0 // number of units of silicate
var/fulltile = FALSE // Set to true on full-tile variants.
+ rad_insulation = RAD_VERY_LIGHT_INSULATION //Windows can have multiple placed on one tile, meaning you have to account for the potential that someone could just build a bunch of windows on one tile to prevent rads entirely.
/obj/structure/window/examine(mob/user)
. = ..()
@@ -514,6 +515,7 @@
maxhealth = 80
fulltile = TRUE
flags = NONE
+ rad_insulation = RAD_LIGHT_INSULATION
/obj/structure/window/phoronreinforced
name = "reinforced borosilicate window"
@@ -527,12 +529,14 @@
damage_per_fire_tick = 1.0 // This should last for 80 fire ticks if the window is not damaged at all. The idea is that borosilicate windows have something like ablative layer that protects them for a while.
maxhealth = 80.0
force_threshold = 10
+ rad_insulation = RAD_LIGHT_INSULATION
/obj/structure/window/phoronreinforced/full
icon_state = "phoronrwindow-full"
maxhealth = 160
fulltile = TRUE
flags = NONE
+ rad_insulation = RAD_MEDIUM_INSULATION
/obj/structure/window/reinforced
name = "reinforced window"
@@ -578,6 +582,7 @@
basestate = "w"
dir = 5
force_threshold = 7
+ rad_insulation = RAD_MEDIUM_INSULATION
/obj/structure/window/reinforced/polarized
name = "electrochromic window"
diff --git a/code/game/objects/structures/window_vr.dm b/code/game/objects/structures/window_vr.dm
index 10b995766f..93d854f8bf 100644
--- a/code/game/objects/structures/window_vr.dm
+++ b/code/game/objects/structures/window_vr.dm
@@ -11,6 +11,7 @@
damage_per_fire_tick = 1.0
maxhealth = 100.0
force_threshold = 10
+ rad_insulation = RAD_EXTREME_INSULATION
/obj/structure/window/titanium/full
icon_state = "window-full"
@@ -30,6 +31,7 @@
damage_per_fire_tick = 1.0
maxhealth = 120.0
force_threshold = 10
+ rad_insulation = RAD_EXTREME_INSULATION
/obj/structure/window/plastitanium/full
icon_state = "window-full"
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 7951a6726c..223d58f214 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -306,6 +306,10 @@
soundin = pick(
'sound/effects/mech/powerloader_step.ogg',
'sound/effects/mech/powerloader_step2.ogg')
+ if ("sizzle")
+ soundin = pick(
+ 'sound/effects/wounds/sizzle1.ogg',
+ 'sound/effects/wounds/sizzle2.ogg')
return soundin
diff --git a/code/game/turfs/simulated/floor_types.dm b/code/game/turfs/simulated/floor_types.dm
index 98bcf8c10d..7bdca24bb3 100644
--- a/code/game/turfs/simulated/floor_types.dm
+++ b/code/game/turfs/simulated/floor_types.dm
@@ -91,6 +91,7 @@
var/join_flags = 0 //Bitstring to represent adjacency of joining walls
var/join_group = "shuttle" //A tag for what other walls to join with. Null if you don't want them to.
var/static/list/antilight_cache
+ rad_insulation = RAD_MEDIUM_INSULATION
/turf/simulated/shuttle/Initialize(mapload)
. = ..()
@@ -312,6 +313,36 @@
/turf/simulated/floor/tiled/material/uranium
icon_state = "uranium"
initial_flooring = /datum/decl/flooring/tiling/material/uranium
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
+
+/turf/simulated/floor/tiled/material/uranium/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_ATOM_PROPAGATE_RAD_PULSE, PROC_REF(radiate))
+
+/turf/simulated/floor/tiled/material/uranium/Destroy()
+ UnregisterSignal(src, COMSIG_ATOM_PROPAGATE_RAD_PULSE)
+ . = ..()
+
+/turf/simulated/floor/tiled/material/uranium/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 1,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 1
+ )
+ propagate_radiation_pulse()
+ last_event = world.time
+ active = FALSE
/datum/decl/flooring/tiling/material/uranium
name = "uranium floor"
diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm
index a1b8d4ca3b..6fa5af92f9 100644
--- a/code/game/turfs/simulated/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall_attacks.dm
@@ -4,8 +4,6 @@
if(can_open == WALL_OPENING)
return
- SSradiation.resistance_cache.Remove(src)
-
if(density)
can_open = WALL_OPENING
//flick("[material.icon_base]fwall_opening", src)
diff --git a/code/game/turfs/simulated/wall_icon.dm b/code/game/turfs/simulated/wall_icon.dm
index 7b75ad3333..49610b560e 100644
--- a/code/game/turfs/simulated/wall_icon.dm
+++ b/code/game/turfs/simulated/wall_icon.dm
@@ -26,7 +26,6 @@
else if(material.opacity < 0.5 && opacity)
set_light(0)
- SSradiation.resistance_cache.Remove(src)
update_connections(1)
update_icon()
diff --git a/code/game/turfs/simulated/wall_types.dm b/code/game/turfs/simulated/wall_types.dm
index 19fa6f6450..b19d794fb9 100644
--- a/code/game/turfs/simulated/wall_types.dm
+++ b/code/game/turfs/simulated/wall_types.dm
@@ -1,5 +1,6 @@
/turf/simulated/wall/r_wall
icon_state = "rgeneric"
+ rad_insulation = RAD_HEAVY_INSULATION
/turf/simulated/wall/r_wall/Initialize(mapload)
. = ..(mapload, MAT_PLASTEEL,MAT_PLASTEEL) //3strong
@@ -9,30 +10,44 @@
. = ..(mapload, MAT_STEELHULL, null, MAT_STEELHULL)
/turf/simulated/wall/rshull
icon_state = "hull-r_steel"
+ rad_insulation = RAD_HEAVY_INSULATION
/turf/simulated/wall/rshull/Initialize(mapload)
. = ..(mapload, MAT_STEELHULL, MAT_STEELHULL, MAT_STEELHULL)
/turf/simulated/wall/pshull
icon_state = "hull-plasteel"
+ rad_insulation = RAD_HEAVY_INSULATION
/turf/simulated/wall/pshull/Initialize(mapload) //Spaaaace-er ship.
. = ..(mapload, MAT_PLASTEELHULL, null, MAT_PLASTEELHULL)
/turf/simulated/wall/rpshull
icon_state = "hull-r_plasteel"
/turf/simulated/wall/rpshull/Initialize(mapload)
. = ..(mapload, MAT_PLASTEELHULL, MAT_PLASTEELHULL, MAT_PLASTEELHULL)
+
/turf/simulated/wall/dshull
icon_state = "hull-durasteel"
+ rad_insulation = RAD_HEAVY_INSULATION
+
/turf/simulated/wall/dshull/Initialize(mapload) //Spaaaace-est ship.
. = ..(mapload, MAT_DURASTEELHULL, null, MAT_DURASTEELHULL)
+
/turf/simulated/wall/rdshull
icon_state = "hull-r_durasteel"
+ rad_insulation = RAD_EXTREME_INSULATION
+
/turf/simulated/wall/rdshull/Initialize(mapload)
. = ..(mapload, MAT_DURASTEELHULL, MAT_DURASTEELHULL, MAT_DURASTEELHULL)
+
/turf/simulated/wall/thull
icon_state = "hull-titanium"
+ rad_insulation = RAD_HEAVY_INSULATION
+
/turf/simulated/wall/thull/Initialize(mapload)
. = ..(mapload, MAT_TITANIUMHULL, null, MAT_TITANIUMHULL)
+
/turf/simulated/wall/rthull
icon_state = "hull-r_titanium"
+ rad_insulation = RAD_EXTREME_INSULATION
+
/turf/simulated/wall/rthull/Initialize(mapload)
. = ..(mapload, MAT_TITANIUMHULL, MAT_TITANIUMHULL, MAT_TITANIUMHULL)
@@ -56,8 +71,16 @@
. = ..(mapload, MAT_GOLD)
/turf/simulated/wall/silver/Initialize(mapload)
. = ..(mapload, MAT_SILVER)
+
+/turf/simulated/wall/lead
+ rad_insulation = RAD_EXTREME_INSULATION
+
/turf/simulated/wall/lead/Initialize(mapload)
. = ..(mapload, MAT_LEAD)
+
+/turf/simulated/wall/r_lead
+ rad_insulation = RAD_EXTREME_INSULATION
+
/turf/simulated/wall/r_lead/Initialize(mapload)
. = ..(mapload, MAT_LEAD, MAT_LEAD)
/turf/simulated/wall/phoron/Initialize(mapload)
@@ -80,11 +103,13 @@
/turf/simulated/wall/concrete
icon_state = "brick"
+ rad_insulation = RAD_HEAVY_INSULATION
/turf/simulated/wall/concrete/Initialize(mapload)
. = ..(mapload, MAT_CONCRETE) //3strong
/turf/simulated/wall/r_concrete
+ rad_insulation = RAD_HEAVY_INSULATION
icon_state = "rbrick"
/turf/simulated/wall/r_concrete/Initialize(mapload)
@@ -98,6 +123,9 @@
/turf/simulated/wall/titanium/Initialize(mapload)
. = ..(mapload, MAT_TITANIUM)
+/turf/simulated/wall/durasteel
+ rad_insulation = RAD_HEAVY_INSULATION
+
/turf/simulated/wall/durasteel/Initialize(mapload)
. = ..(mapload, MAT_DURASTEEL, MAT_DURASTEEL)
diff --git a/code/game/turfs/simulated/wall_types_vr.dm b/code/game/turfs/simulated/wall_types_vr.dm
index e1e6bb3a4c..6d7dc3d55b 100644
--- a/code/game/turfs/simulated/wall_types_vr.dm
+++ b/code/game/turfs/simulated/wall_types_vr.dm
@@ -215,6 +215,33 @@ GLOBAL_LIST_EMPTY(flesh_overlay_cache)
/turf/simulated/wall/uranium
icon_state = "uranium"
icon = 'icons/turf/wall_masks_vr.dmi'
+ var/last_event = 0
+
+/turf/simulated/wall/uranium/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_ATOM_PROPAGATE_RAD_PULSE, PROC_REF(radiate))
+
+/turf/simulated/wall/uranium/Destroy()
+ UnregisterSignal(src, COMSIG_ATOM_PROPAGATE_RAD_PULSE)
+ . = ..()
+
+/turf/simulated/wall/uranium/radiate()
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 5
+ )
+ propagate_radiation_pulse()
+ last_event = world.time
+ active = FALSE
/turf/simulated/wall/virgo2
icon_state = "virgo2"
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index d6303f3c40..fc9357c46a 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -23,6 +23,7 @@
// There's basically always going to be wall connections, making this lazy doesn't seem like it'd help much unless you wanted to make it bitflags instead.
var/list/wall_connections = list("0", "0", "0", "0")
+ rad_insulation = RAD_MEDIUM_INSULATION
// Walls always hide the stuff below them.
/turf/simulated/wall/levelupdate()
@@ -297,11 +298,19 @@
return
/turf/simulated/wall/proc/radiate()
+ SIGNAL_HANDLER
var/total_radiation = material.radioactivity + (reinf_material ? reinf_material.radioactivity / 2 : 0) + (girder_material ? girder_material.radioactivity / 2 : 0)
if(!total_radiation)
return
- SSradiation.radiate(src, total_radiation)
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = total_radiation,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = total_radiation
+ )
return total_radiation
/turf/simulated/wall/proc/burn(temperature)
diff --git a/code/modules/blob2/overmind/types/radioactive_ooze.dm b/code/modules/blob2/overmind/types/radioactive_ooze.dm
index 32309ba858..6ea00ca048 100644
--- a/code/modules/blob2/overmind/types/radioactive_ooze.dm
+++ b/code/modules/blob2/overmind/types/radioactive_ooze.dm
@@ -21,7 +21,20 @@
attack_verb = "splashes"
/datum/blob_type/radioactive_ooze/on_pulse(var/obj/structure/blob/B)
- SSradiation.radiate(B, 200)
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 75
+ )
/datum/blob_type/radioactive_ooze/on_chunk_tick(obj/item/blobcore_chunk/B)
- SSradiation.radiate(B, rand(25,100))
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE * 0.5,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 25
+ )
diff --git a/code/modules/clothing/spacesuits/rig/modules/specific/passive_protection.dm b/code/modules/clothing/spacesuits/rig/modules/specific/passive_protection.dm
index 338e19a4c0..b1edef7678 100644
--- a/code/modules/clothing/spacesuits/rig/modules/specific/passive_protection.dm
+++ b/code/modules/clothing/spacesuits/rig/modules/specific/passive_protection.dm
@@ -34,17 +34,26 @@
var/obj/item/clothing/head/helmet/space/rig/helmet = holder.helmet
var/obj/item/clothing/gloves/gauntlets/rig/gloves = holder.gloves
+ var/list/items_to_update = list()
+
to_chat(H, span_boldnotice("You activate your suit's powered radiation shielding."))
stored_rad_armor = holder.armor["rad"]
if(boots)
boots.armor["rad"] = 100
+ items_to_update += boots
if(chest)
chest.armor["rad"] = 100
+ items_to_update += chest
if(helmet)
helmet.armor["rad"] = 100
+ items_to_update += helmet
if(gloves)
gloves.armor["rad"] = 100
+ items_to_update += gloves
holder.armor["rad"] = 100
+ items_to_update += holder
+ for(var/obj/item/part in items_to_update)
+ ADD_TRAIT(part, TRAIT_RADIATION_PROTECTED_CLOTHING, MOD_TRAIT)
/obj/item/rig_module/rad_shield/deactivate()
@@ -57,20 +66,30 @@
var/obj/item/clothing/head/helmet/space/rig/helmet = holder.helmet
var/obj/item/clothing/gloves/gauntlets/rig/gloves = holder.gloves
+ var/list/items_to_update = list()
+
to_chat(H, span_danger("You deactivate your suit's powered radiation shielding."))
if(boots)
boots.armor["rad"] = stored_rad_armor
+ items_to_update += boots
if(chest)
chest.armor["rad"] = stored_rad_armor
+ items_to_update += chest
if(helmet)
helmet.armor["rad"] = stored_rad_armor
+ items_to_update += helmet
if(gloves)
gloves.armor["rad"] = stored_rad_armor
+ items_to_update += gloves
holder.armor["rad"] = stored_rad_armor
+ items_to_update += holder
stored_rad_armor = 0
+ for(var/obj/item/part in items_to_update)
+ REMOVE_TRAIT(part, TRAIT_RADIATION_PROTECTED_CLOTHING, MOD_TRAIT)
+
/obj/item/rig_module/rad_shield/advanced
name = "advanced radiation absorption device"
desc = "The acronym of this device - R.A.D. - and its full name both convey the application of the module. It has a changelog inscribed \
diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm
index 4bfd3584b5..d52fbcbeef 100644
--- a/code/modules/clothing/suits/utility.dm
+++ b/code/modules/clothing/suits/utility.dm
@@ -88,6 +88,11 @@
body_parts_covered = HEAD|FACE|EYES
armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 60, rad = 100)
+
+/obj/item/clothing/head/radiation/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/radiation_protected_clothing)
+
/obj/item/clothing/suit/radiation
name = "Radiation suit"
desc = "A suit that protects against radiation. Label: Made with lead, do not eat insulation."
@@ -102,6 +107,10 @@
flags_inv = HIDEJUMPSUIT|HIDETAIL|HIDETIE|HIDEHOLSTER
item_flags = THICKMATERIAL
+/obj/item/clothing/suit/radiation/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/radiation_protected_clothing)
+
/obj/item/clothing/suit/radiation/teshari
name = "Small radiation suit"
desc = "A specialist suit that protects against radiation, designed specifically for use by Teshari. Made to order by Aether."
diff --git a/code/modules/economy/coins.dm b/code/modules/economy/coins.dm
index 7deecd55b6..1becc2a232 100644
--- a/code/modules/economy/coins.dm
+++ b/code/modules/economy/coins.dm
@@ -96,6 +96,38 @@
desc = "A " + MAT_URANIUM + " coin. You probably don't want to store this in your pants pocket..."
icon_state = "coin_uranium"
matter = list(MAT_URANIUM = 250)
+ var/last_event = 0
+ var/active = 0
+
+/obj/item/coin/uranium/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/item/coin/uranium/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ . = ..()
+
+/obj/item/coin/uranium/process()
+ radiate()
+ ..()
+
+/obj/item/coin/uranium/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 1,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 2
+ )
+ last_event = world.time
+ active = FALSE
/obj/item/coin/platinum
name = MAT_PLATINUM + " coin"
diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm
index df9ce8af95..95bee85082 100644
--- a/code/modules/events/radiation_storm.dm
+++ b/code/modules/events/radiation_storm.dm
@@ -29,9 +29,21 @@
else if(activeFor == leaveBelt)
GLOB.command_announcement.Announce("The [using_map.facility_type] has passed the radiation belt. Please allow for up to one minute while radiation levels dissipate, and report to medbay if you experience any unusual symptoms. Maintenance will lose all access again shortly.", "Anomaly Alert")
/datum/event/radiation_storm/proc/radiate()
+
+ //This sucks. Just mutate.
+ /*
var/radiation_level = rand(15, 35)
for(var/z in using_map.station_levels)
- SSradiation.z_radiate(locate(1, 1, z), radiation_level, 1)
+ var/turf/epicentre = locate(round(world.maxx / 2), round(world.maxy / 2), z)
+ if(epicentre)
+ radiation_pulse(
+ epicentre,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ )
+ */
for(var/mob/living/carbon/C in GLOB.living_mob_list)
if(!(C.z in using_map.station_levels) || C.isSynthetic() || isbelly(C.loc))
diff --git a/code/modules/events/solar_storm.dm b/code/modules/events/solar_storm.dm
index 2cdf2ff8b5..26a16170d6 100644
--- a/code/modules/events/solar_storm.dm
+++ b/code/modules/events/solar_storm.dm
@@ -37,7 +37,13 @@
continue
//Todo: Apply some burn damage from the heat of the sun. Until then, enjoy some moderate radiation.
- L.rad_act(rand(15, 30))
+ radiation_pulse(
+ L,
+ max_range = 1,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = rand(10, 50)
+ )
/datum/event/solar_storm/end()
GLOB.command_announcement.Announce("The solar storm has passed the [using_map.facility_type]. It is now safe to resume EVA activities. Please report to medbay if you experience any unusual symptoms. ", "Anomaly Alert")
diff --git a/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm b/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm
index 84db701727..a5e2ecec04 100644
--- a/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm
+++ b/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm
@@ -29,9 +29,41 @@
radiate()
/datum/event2/event/radiation_storm/proc/radiate()
+ //This sucks. Just mutate.
+ /*
var/radiation_level = rand(15, 35)
for(var/z in using_map.station_levels)
- SSradiation.z_radiate(locate(1, 1, z), radiation_level, 1)
+ var/turf/epicentre = locate(round(world.maxx / 2), round(world.maxy / 2), z)
+ if(epicentre)
+ radiation_pulse(
+ epicentre,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ )
+ */
+
+ for(var/mob/living/carbon/C in GLOB.living_mob_list)
+ if(!(C.z in using_map.station_levels) || C.isSynthetic() || isbelly(C.loc))
+ continue
+ var/area/A = get_area(C)
+ if(!A)
+ continue
+ if(A.flag_check(RAD_SHIELDED))
+ continue
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ var/chance = 5.0
+ chance -= (chance / 100) * C.getarmor(null, "rad")
+ if(prob(chance))
+ if (prob(75))
+ randmutb(H) // Applies bad mutation
+ domutcheck(H,null,MUTCHK_FORCED)
+ else
+ randmutg(H) // Applies good mutation
+ domutcheck(H,null,MUTCHK_FORCED)
+ H.UpdateAppearance()
/datum/event2/event/radiation_storm/end()
GLOB.command_announcement.Announce("The station has passed the radiation belt. \
diff --git a/code/modules/gamemaster/event2/events/everyone/solar_storm.dm b/code/modules/gamemaster/event2/events/everyone/solar_storm.dm
index 15c3ab66b5..c3867e83f2 100644
--- a/code/modules/gamemaster/event2/events/everyone/solar_storm.dm
+++ b/code/modules/gamemaster/event2/events/everyone/solar_storm.dm
@@ -49,4 +49,10 @@
continue
//Todo: Apply some burn damage from the heat of the sun. Until then, enjoy some moderate radiation.
- L.rad_act(rand(15, 30))
+ radiation_pulse(
+ L,
+ max_range = 1,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = rand(10, 50)
+ )
diff --git a/code/modules/materials/sheets/supermatter.dm b/code/modules/materials/sheets/supermatter.dm
index 3e3eab421e..448d6e2a13 100644
--- a/code/modules/materials/sheets/supermatter.dm
+++ b/code/modules/materials/sheets/supermatter.dm
@@ -5,6 +5,40 @@
item_state = "diamond"
default_type = MAT_SUPERMATTER
apply_colour = TRUE
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
+
+/obj/item/stack/material/supermatter/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/item/stack/material/supermatter/process()
+ radiate()
+ ..()
+
+/obj/item/stack/material/supermatter/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = (amount * 0.2), //10 range at 50 amount
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = NEBULA_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = amount * 0.5 //2 sheets = 1 rad, 50 sheets = 25 rads.
+ )
+ last_event = world.time
+ active = FALSE
+
+/obj/item/stack/material/supermatter/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
/obj/item/stack/material/supermatter/proc/update_mass() // Due to how dangerous they can be, the item will get heavier and larger the more are in the stack.
slowdown = amount / 10
@@ -20,7 +54,6 @@
. = ..()
update_mass()
- SSradiation.radiate(src, 5 + amount)
var/mob/living/M = user
if(!istype(M))
return
@@ -47,9 +80,15 @@
/obj/item/stack/material/supermatter/ex_act(severity) // An incredibly hard to manufacture material, SM chunks are unstable by their 'stabilized' nature.
if(prob((4 / severity) * 20))
- SSradiation.radiate(get_turf(src), amount * 4)
+ radiation_pulse(
+ src,
+ max_range = amount,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE * 5,
+ minimum_exposure_time = 0,
+ strength = amount * 10
+ )
explosion(get_turf(src),round(amount / 12) , round(amount / 6), round(amount / 3), round(amount / 25))
qdel(src)
return
- SSradiation.radiate(get_turf(src), amount * 2)
..()
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 7826655573..8de0b393d1 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -641,7 +641,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
gas_analyzing += "Temperature: [round(environment.temperature-T0C,0.1)]°C ([round(environment.temperature,0.1)]K)"
gas_analyzing += "Heat Capacity: [round(environment.heat_capacity(),0.1)]"
to_chat(src, span_notice("[jointext(gas_analyzing, "
")]"))
-
+/*
/mob/observer/dead/verb/check_radiation()
set name = "Check Radiation"
set category = "Ghost.Game"
@@ -650,7 +650,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(t)
var/rads = SSradiation.get_rads_at_turf(t)
to_chat(src, span_notice("Radiation level: [rads ? rads : "0"] Bq."))
-
+*/
/mob/observer/dead/verb/view_manfiest()
set name = "Show Crew Manifest"
set category = "Ghost.Game"
diff --git a/code/modules/mob/living/carbon/brain/life.dm b/code/modules/mob/living/carbon/brain/life.dm
index 4ae47e2aed..4131327abd 100644
--- a/code/modules/mob/living/carbon/brain/life.dm
+++ b/code/modules/mob/living/carbon/brain/life.dm
@@ -6,13 +6,13 @@
if(.)
return
if (radiation)
- if (radiation > 100)
+ throw_alert("irradiated", /atom/movable/screen/alert/irradiated)
+ if(radiation > 100)
radiation = 100
if(!container)//If it's not in an MMI
to_chat(src, span_red("You feel weak."))
else//Fluff-wise, since the brain can't detect anything itself, the MMI handles thing like that
to_chat(src, span_red("STATUS: CRITICAL AMOUNTS OF RADIATION DETECTED."))
-
switch(radiation)
if(1 to 49)
radiation--
@@ -35,6 +35,8 @@
radiation -= 3
adjustToxLoss(3)
updatehealth()
+ else
+ clear_alert("irradiated")
/mob/living/carbon/brain/handle_environment(datum/gas_mixture/environment)
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index dfec991111..17f54d7ff3 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -251,7 +251,7 @@
/mob/living/carbon/human/inStasisNow()
var/stasisValue = getStasis()
- if(stasisValue && (life_tick % stasisValue))
+ if(stasisValue && (life_tick % stasisValue) || HAS_TRAIT(src, TRAIT_STASIS))
return 1
return 0
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 6ca494b001..5a787b0390 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -610,3 +610,11 @@ emp_act
return TRUE
return FALSE
+
+///Get all the clothing on a specific body part
+/mob/living/carbon/human/proc/get_clothing_on_part(obj/item/organ/external/def_zone)
+ var/list/covering_part = list()
+ for(var/obj/item/clothing/equipped in get_equipped_items(INCLUDE_ABSTRACT))
+ if(equipped.body_parts_covered & def_zone.body_part)
+ covering_part += equipped
+ return covering_part
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 0e18821f74..e8888d9c2a 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -47,37 +47,38 @@
voice = GetVoice()
- var/stasis = inStasisNow()
+ var/stasis = (inStasisNow())
if(getStasis() > 2)
Sleeping(20)
//No need to update all of these procs if the guy is dead.
fall() //Prevents people from floating
- if(stat != DEAD && !stasis)
- //Updates the number of stored chemicals for powers
- handle_changeling()
+ if(!stasis)
+ if(stat != DEAD)
+ //Updates the number of stored chemicals for powers
+ handle_changeling()
- //Organs and blood
- handle_organs()
- stabilize_body_temperature() //Body temperature adjusts itself (self-regulation)
- weightgain()
- handle_shock()
+ //Organs and blood
+ handle_organs()
+ stabilize_body_temperature() //Body temperature adjusts itself (self-regulation)
+ weightgain()
+ handle_shock()
- handle_pain()
+ handle_pain()
- SEND_SIGNAL(src,COMSIG_HANDLE_ALLERGENS, chem_effects[CE_ALLERGEN])
+ SEND_SIGNAL(src,COMSIG_HANDLE_ALLERGENS, chem_effects[CE_ALLERGEN])
- handle_medical_side_effects()
+ handle_medical_side_effects()
- handle_heartbeat()
- handle_nif()
- if(phobias)
- handle_phobias()
- if(!client)
- species.handle_npc(src)
+ handle_heartbeat()
+ handle_nif()
+ if(phobias)
+ handle_phobias()
+ if(!client)
+ species.handle_npc(src)
- else if(stat == DEAD && !stasis)
- handle_defib_timer()
+ else if(stat == DEAD)
+ handle_defib_timer()
//Handle any species related components we may have. Ex: Shadekin, Xenochimera, etc. Not STAT checked because those do statchecks in their own code.
handle_species_components()
@@ -262,11 +263,12 @@
accumulated_rads = CLAMP(accumulated_rads,0,RADIATION_CAP) //Max of 100Gy as well. You should never get higher than this. You will be dead before you can reach this.
var/obj/item/organ/internal/I = null //Used for further down below when an organ is picked.
if(!radiation)
+ clear_alert("irradiated")
if(accumulated_rads)
accumulated_rads -= RADIATION_SPEED_COEFFICIENT //Accumulated rads slowly dissipate very slowly. Get to medical to get it treated!
else if(((life_tick % 5 == 0) && radiation) || (radiation > 600)) //Radiation is a slow, insidious killer. Unless you get a massive dose, then the onset is sudden!
- if(reagents.has_reagent(REAGENT_ID_PRUSSIANBLUE)) //Prussian Blue temporarily stops radiation effects.
+ if(HAS_TRAIT(src, TRAIT_HALT_RADIATION_EFFECTS)) //If we have a trait that halts radiation effects, then we just stop here. No need to do any of the checks below.
return
var/damage = 0
@@ -334,6 +336,7 @@
else if (radiation >= GLOB.radiation_levels[species.rad_levels]["danger_3"] && radiation < GLOB.radiation_levels[species.rad_levels]["danger_4"]) //Equivalent of 8.0 to 30 Gy.
+ throw_alert("irradiated", /atom/movable/screen/alert/irradiated)
damage = 10
radiation -= 100 * RADIATION_SPEED_COEFFICIENT * species.rad_removal_mod
accumulated_rads += 100 * RADIATION_SPEED_COEFFICIENT
diff --git a/code/modules/mob/living/carbon/human/species/station/prommie_blob.dm b/code/modules/mob/living/carbon/human/species/station/prommie_blob.dm
index fbb197d1cc..a7d8e616b9 100644
--- a/code/modules/mob/living/carbon/human/species/station/prommie_blob.dm
+++ b/code/modules/mob/living/carbon/human/species/station/prommie_blob.dm
@@ -206,13 +206,6 @@
else
return ..()
-/mob/living/simple_mob/slime/promethean/rad_act(severity)
- rad_glow += severity
- rad_glow = CLAMP(rad_glow,0,250)
- if(rad_glow > 1)
- set_light(max(1,min(5,rad_glow/15)), max(1,min(10,rad_glow/25)), color)
- update_icon()
-
/mob/living/simple_mob/slime/promethean/bullet_act(obj/item/projectile/P)
if(humanform)
return humanform.bullet_act(P)
diff --git a/code/modules/mob/living/carbon/human/species/station/protean/protean_blob.dm b/code/modules/mob/living/carbon/human/species/station/protean/protean_blob.dm
index 3546289902..acb859bbe2 100644
--- a/code/modules/mob/living/carbon/human/species/station/protean/protean_blob.dm
+++ b/code/modules/mob/living/carbon/human/species/station/protean/protean_blob.dm
@@ -307,14 +307,6 @@
else
return ..()
-/mob/living/simple_mob/protean_blob/rad_act(severity)
- if(istype(loc, /obj/item/rig))
- return //Don't irradiate us while we're in rig mode
- if(humanform)
- return humanform.rad_act(severity)
- else
- return ..()
-
/mob/living/simple_mob/protean_blob/bullet_act(obj/item/projectile/P)
if(humanform)
return humanform.bullet_act(P)
diff --git a/code/modules/mob/living/carbon/human/species/station/traits/positive.dm b/code/modules/mob/living/carbon/human/species/station/traits/positive.dm
index b7bcddaf48..1d17dd83d0 100644
--- a/code/modules/mob/living/carbon/human/species/station/traits/positive.dm
+++ b/code/modules/mob/living/carbon/human/species/station/traits/positive.dm
@@ -442,6 +442,14 @@
hidden = FALSE
activation_message="Your body feels mundane."
+/datum/trait/positive/rad_immune/apply(var/datum/species/S,var/mob/living/carbon/human/H)
+ ..()
+ ADD_TRAIT(H, TRAIT_RADIMMUNE, ROUNDSTART_TRAIT)
+
+/datum/trait/positive/rad_immune/unapply(var/datum/species/S,var/mob/living/carbon/human/H)
+ ..()
+ REMOVE_TRAIT(H, TRAIT_RADIMMUNE, ROUNDSTART_TRAIT)
+
/datum/trait/positive/vibration_sense
name = "Vibration Sense"
desc = "Allows you to sense subtle vibrations nearby, even if the source cannot be seen."
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index cb07ed75ef..42ec5a123a 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -32,7 +32,6 @@
return name
/mob/living/Destroy()
- SSradiation.listeners -= src
remove_all_modifiers(TRUE)
QDEL_NULL(say_list)
diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm
index e77781bc5c..45d3f45b04 100644
--- a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm
@@ -522,8 +522,14 @@
..()
/mob/living/simple_mob/slime/xenobio/green/proc/irradiate()
- SSradiation.radiate(src, rads)
-
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = rads * 0.5,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rads
+ )
/mob/living/simple_mob/slime/xenobio/pink
diff --git a/code/modules/planet/virgo3b_vr.dm b/code/modules/planet/virgo3b_vr.dm
index 2022871e4c..147d87a36c 100644
--- a/code/modules/planet/virgo3b_vr.dm
+++ b/code/modules/planet/virgo3b_vr.dm
@@ -578,7 +578,13 @@ GLOBAL_DATUM(planet_virgo3b, /datum/planet/virgo3b)
if(!T.is_outdoors())
return // They're indoors, so no need to irradiate them with fallout.
- L.rad_act(rand(direct_rad_low, direct_rad_high))
+ radiation_pulse(
+ L,
+ max_range = 1,
+ threshold = RAD_VERY_LIGHT_INSULATION,
+ chance = rand(fallout_rad_low, fallout_rad_high),
+ strength = rand(fallout_rad_low, fallout_rad_high)
+ )
// This makes random tiles near people radioactive for awhile.
// Tiles far away from people are left alone, for performance.
@@ -590,7 +596,14 @@ GLOBAL_DATUM(planet_virgo3b, /datum/planet/virgo3b)
if(!istype(T))
return
if(T.is_outdoors())
- SSradiation.radiate(T, rand(fallout_rad_low, fallout_rad_high))
+ radiation_pulse(
+ T,
+ max_range = 7,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rand(fallout_rad_low, fallout_rad_high)
+ )
/datum/weather/virgo3b/fallout/temp
name = "short-term fallout"
diff --git a/code/modules/planet/virgo3c_vr.dm b/code/modules/planet/virgo3c_vr.dm
index 080a61d584..24bd52791b 100644
--- a/code/modules/planet/virgo3c_vr.dm
+++ b/code/modules/planet/virgo3c_vr.dm
@@ -582,7 +582,14 @@ GLOBAL_DATUM(planet_virgo3c, /datum/planet/virgo3c)
if(!T.is_outdoors())
return // They're indoors, so no need to irradiate them with fallout.
- L.rad_act(rand(direct_rad_low, direct_rad_high))
+ radiation_pulse(
+ L,
+ max_range = 1,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = rand(direct_rad_low, direct_rad_high),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rand(direct_rad_low, direct_rad_high)
+ )
// This makes random tiles near people radioactive for awhile.
// Tiles far away from people are left alone, for performance.
@@ -594,7 +601,14 @@ GLOBAL_DATUM(planet_virgo3c, /datum/planet/virgo3c)
if(!istype(T))
return
if(T.is_outdoors())
- SSradiation.radiate(T, rand(fallout_rad_low, fallout_rad_high))
+ radiation_pulse(
+ T,
+ max_range = 1,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = rand(direct_rad_low, direct_rad_high),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rand(fallout_rad_low, fallout_rad_high)
+ )
/datum/weather/virgo3c/fallout/temp
name = "short-term fallout"
diff --git a/code/modules/planet/virgo4_vr.dm b/code/modules/planet/virgo4_vr.dm
index f007769878..b7eb3cbf1f 100644
--- a/code/modules/planet/virgo4_vr.dm
+++ b/code/modules/planet/virgo4_vr.dm
@@ -553,7 +553,15 @@ GLOBAL_DATUM(planet_virgo4, /datum/planet/virgo4)
if(!T.is_outdoors())
return // They're indoors, so no need to irradiate them with fallout.
- L.rad_act(rand(direct_rad_low, direct_rad_high))
+ radiation_pulse(
+ L,
+ max_range = 1,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = rand(direct_rad_low, direct_rad_high),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rand(direct_rad_low, direct_rad_high)
+ )
+
// This makes random tiles near people radioactive for awhile.
// Tiles far away from people are left alone, for performance.
@@ -565,7 +573,14 @@ GLOBAL_DATUM(planet_virgo4, /datum/planet/virgo4)
if(!istype(T))
return
if(T.is_outdoors())
- SSradiation.radiate(T, rand(fallout_rad_low, fallout_rad_high))
+ radiation_pulse(
+ T,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = rand(direct_rad_low, direct_rad_high),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = rand(fallout_rad_low, fallout_rad_high)
+ )
/datum/weather/virgo4/fallout/temp
name = "short-term fallout"
diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/power/fusion/core/core_field.dm
index 64a9f5c2cc..558af0a245 100644
--- a/code/modules/power/fusion/core/core_field.dm
+++ b/code/modules/power/fusion/core/core_field.dm
@@ -333,7 +333,13 @@
radiation += plasma_temperature/2
plasma_temperature = 0
- SSradiation.radiate(src, radiation)
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = energy * 0.001 //Might need to be increased.
+ )
Radiate()
/obj/effect/fusion_em_field/proc/Radiate()
@@ -539,7 +545,13 @@
//Reaction radiation is fairly buggy and there's at least three procs dealing with radiation here, this is to ensure constant radiation output.
/obj/effect/fusion_em_field/proc/radiation_scale()
- SSradiation.radiate(src, 2 + plasma_temperature / PLASMA_TEMP_RADIATION_DIVISIOR)
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = (URANIUM_IRRADIATION_CHANCE + (plasma_temperature / PLASMA_TEMP_RADIATION_DIVISIOR)),
+ strength = energy * 0.01 //Might need to be increased.
+ )
//Somehow fixing the radiation issue managed to break this, but moving it to it's own proc seemed to have fixed it. I don't know.
/obj/effect/fusion_em_field/proc/temp_dump()
diff --git a/code/modules/power/fusion/fuel_assembly/fuel_assembly.dm b/code/modules/power/fusion/fuel_assembly/fuel_assembly.dm
index b86675cda5..9caf5cb4bb 100644
--- a/code/modules/power/fusion/fuel_assembly/fuel_assembly.dm
+++ b/code/modules/power/fusion/fuel_assembly/fuel_assembly.dm
@@ -11,6 +11,34 @@
var/fuel_colour
var/radioactivity = 0
var/const/initial_amount = 3000000
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
+
+/obj/item/fuel_assembly/process()
+ radiate()
+
+/obj/item/fuel_assembly/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = (radioactivity * 0.5),
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DEFAULT_RADIATION_CHANCE,
+ strength = radioactivity * 0.5
+ )
+ last_event = world.time
+ active = FALSE
+
+/obj/item/fuel_assembly/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
/obj/item/fuel_assembly/Initialize(mapload, var/_material, var/_colour)
. = ..()
@@ -38,17 +66,6 @@
add_overlay(list(I, image(icon, "fuel_assembly_bracket")))
rod_quantities[fuel_type] = initial_amount
-/obj/item/fuel_assembly/process()
- if(!radioactivity)
- return PROCESS_KILL
-
- if(istype(loc, /turf))
- SSradiation.radiate(src, max(1,CEILING(radioactivity/30, 1)))
-
-/obj/item/fuel_assembly/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
// Mapper shorthand.
/obj/item/fuel_assembly/deuterium/Initialize(mapload)
. = ..(mapload, MAT_DEUTERIUM)
diff --git a/code/modules/power/fusion/fusion_reactions.dm b/code/modules/power/fusion/fusion_reactions.dm
index fd8a257a6a..71606068f5 100644
--- a/code/modules/power/fusion/fusion_reactions.dm
+++ b/code/modules/power/fusion/fusion_reactions.dm
@@ -116,11 +116,13 @@ GLOBAL_LIST(fusion_reactions)
var/turf/origin = get_turf(holder)
holder.Rupture()
- qdel(holder)
- var/radiation_level = 200
-
- // Copied from the SM for proof of concept. //Not any more --Cirra //Use the whole z proc --Leshana
- SSradiation.z_radiate(locate(1, 1, holder.z), radiation_level, 1)
+ radiation_pulse(
+ holder,
+ max_range = 50,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 500
+ )
for(var/mob/living/mob in GLOB.living_mob_list)
var/turf/T = get_turf(mob)
@@ -135,8 +137,7 @@ GLOBAL_LIST(fusion_reactions)
spawn(5)
if(I && I.loc)
qdel(I)
-
- sleep(5)
+ qdel(holder)
explosion(origin, 1, 2, 5)
return 1
diff --git a/code/modules/power/gravitygenerator_vr.dm b/code/modules/power/gravitygenerator_vr.dm
index 5ac55be346..1f7f706067 100644
--- a/code/modules/power/gravitygenerator_vr.dm
+++ b/code/modules/power/gravitygenerator_vr.dm
@@ -378,7 +378,14 @@ GLOBAL_LIST_EMPTY(gravity_generators)
/obj/machinery/gravity_generator/main/proc/pulse_radiation()
- SSradiation.radiate(src, 200)
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DEFAULT_RADIATION_CHANCE * 2,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = charge_count * 2
+ )
/obj/machinery/gravity_generator/main/proc/update_gravity(var/on)
for(var/area/A in src.areas)
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index d3af5df9f9..26c29e0ea8 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -385,13 +385,25 @@
/obj/machinery/power/port_gen/pacman/super/UseFuel()
//produces a tiny amount of radiation when in use
if (prob(2*power_output))
- SSradiation.radiate(src, 4)
+ radiation_pulse(
+ src,
+ max_range = 2,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DEFAULT_RADIATION_CHANCE,
+ strength = power_gen * 0.01
+ )
..()
/obj/machinery/power/port_gen/pacman/super/explode()
//a nice burst of radiation
var/rads = 50 + (sheets + sheet_left)*1.5
- SSradiation.radiate(src, (max(20, rads)))
+ radiation_pulse(
+ src,
+ max_range = (rads/10),
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DEFAULT_RADIATION_CHANCE,
+ strength = rads
+ )
explosion(src.loc, 3, 3, 5, 3)
qdel(src)
diff --git a/code/modules/power/port_gen_vr.dm b/code/modules/power/port_gen_vr.dm
index 4df4693bb3..2ceae99e38 100644
--- a/code/modules/power/port_gen_vr.dm
+++ b/code/modules/power/port_gen_vr.dm
@@ -144,7 +144,14 @@
..()
add_avail(power_gen)
if(panel_open && irradiate)
- SSradiation.radiate(src, 60)
+ radiation_pulse(
+ src,
+ max_range = 3,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = DEFAULT_RADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = power_gen * 0.01 //1000 power = 10 rads. 10000 power = 100 rads. You can get creative with rad collectors if you want.
+ )
/obj/machinery/power/rtg/RefreshParts()
var/part_level = 0
@@ -593,9 +600,15 @@
var/turf/T = get_turf(src)
qdel(src)
if(T)
+ radiation_pulse(
+ T,
+ max_range = 50,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DEFAULT_RADIATION_CHANCE * 3,
+ strength = power_gen * 0.01 ///1MW = 1000 rads. If you blow up a BLACK HOLE ENGINE, you deserve the radiation that comes with it.
+ )
empulse(T, 12, 14, 16, 18)
explosion(T, 7, 12, 18, 20)
- SSradiation.radiate(T, 200)
new /obj/effect/bhole(T)
/obj/machinery/power/rtg/antimatter_core/blob_act(obj/structure/blob/B)
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index 345cb0ccb5..52fec7b098 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -14,33 +14,36 @@
var/active = 0
var/locked = 0
var/drainratio = 1
+ rad_insulation = RAD_EXTREME_INSULATION //It sucks up the radiation. If you're standing behind it, you're pretty safe.
/obj/machinery/power/rad_collector/Initialize(mapload)
. = ..()
GLOB.rad_collectors += src
AddElement(/datum/element/climbable)
+ RegisterSignal(src, COMSIG_IN_RANGE_OF_IRRADIATION, PROC_REF(process_rads))
/obj/machinery/power/rad_collector/Destroy()
GLOB.rad_collectors -= src
+ UnregisterSignal(src, COMSIG_IN_RANGE_OF_IRRADIATION)
return ..()
-/obj/machinery/power/rad_collector/process()
+/obj/machinery/power/rad_collector/proc/process_rads(datum/source, datum/radiation_pulse_information/pulse_information)
+ SIGNAL_HANDLER
//so that we don't zero out the meter if the SM is processed first.
last_power = last_power_new
last_power_new = 0
if(P && active)
- var/rads = SSradiation.get_rads_at_turf(get_turf(src))
- if(rads)
- receive_pulse(rads * 5) //Maths is hard
+ if(pulse_information)
+ var/amount_of_rads = pulse_information.strength
+ receive_pulse((amount_of_rads))
- if(P)
- if(P.air_contents.gas[GAS_PHORON] == 0)
- investigate_log(span_red("out of fuel") + ".","singulo")
- eject()
- else
- P.air_contents.adjust_gas(GAS_PHORON, -0.001*drainratio)
+ if(P.air_contents.gas[GAS_PHORON] == 0)
+ investigate_log(span_red("out of fuel") + ".","singulo")
+ eject()
+ else
+ P.air_contents.adjust_gas(GAS_PHORON, -0.0001*drainratio)
return
@@ -126,12 +129,15 @@
else
update_icons()
+// Continuing here, SM giving us ~170 rads per pulse, a phoron canister full of 30 mols, and * 20 we get:
+// 102000W per collector...So 10 collectors will give us ~1MW.
/obj/machinery/power/rad_collector/proc/receive_pulse(var/pulse_strength)
if(P && active)
var/power_produced = 0
power_produced = P.air_contents.gas[GAS_PHORON]*pulse_strength*20
- add_avail(power_produced)
- last_power_new = power_produced
+ if(power_produced)
+ add_avail(power_produced)
+ last_power_new = power_produced
return
return
diff --git a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
index 83c90926c7..08f2ce8435 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
@@ -148,13 +148,27 @@
/obj/machinery/particle_smasher/process()
if(!src.anchored) // Rapidly loses focus.
if(energy)
- SSradiation.radiate(src, round(((src.energy-150)/50)*5,1))
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = round(((src.energy-150)/50)*5,1),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = energy * 0.1 //60 rads at max energy.
+ )
energy = max(0, energy - 30)
update_icon()
return
if(energy)
- SSradiation.radiate(src, round(((src.energy-150)/50)*5,1))
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = round(((src.energy-150)/50)*5,1),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = energy * 0.1 //60 rads at max energy.
+ )
energy = CLAMP(energy - 5, 0, max_energy)
return
@@ -184,7 +198,14 @@
if(successful_craft)
visible_message(span_warning("\The [src] fizzles."))
if(prob(33)) // Why are you blasting it after it's already done!
- SSradiation.radiate(src, 10 + round(src.energy / 60, 1))
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE + round(src.energy / 60, 1),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = energy * 0.1
+ )
energy = max(0, energy - 30)
update_icon()
return
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 5e5c6ed80f..ee80286e5d 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -403,11 +403,16 @@ GLOBAL_LIST_BOILERPLATE(all_singularities, /obj/singularity)
/obj/singularity/proc/toxmob()
var/toxrange = 10
var/toxdamage = 4
- var/radiation = 15
if (src.energy>200)
toxdamage = round(((src.energy-150)/50)*4,1)
- radiation = round(((src.energy-150)/50)*5,1)
- SSradiation.radiate(src, radiation) //Always radiate at max, so a decent dose of radiation is applied
+ radiation_pulse(
+ src,
+ max_range = 7,
+ threshold = RAD_EXTREME_INSULATION - 0.1,
+ chance = URANIUM_IRRADIATION_CHANCE + round(energy / 60, 1),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 250
+ )
for(var/mob/living/M in view(toxrange, src.loc))
if(SEND_SIGNAL(M, COMSIG_CHECK_FOR_GODMODE) & COMSIG_GODMODE_CANCEL)
return 0 // Cancelled by a component
@@ -450,13 +455,28 @@ GLOBAL_LIST_BOILERPLATE(all_singularities, /obj/singularity)
to_chat(M, span_danger("You hear an unearthly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat."))
to_chat(M, span_danger("You don't even have a moment to react as you are reduced to ashes by the intense radiation."))
M.dust()
- SSradiation.radiate(src, rand(energy))
+
+ radiation_pulse(
+ src,
+ max_range = 10,
+ threshold = RAD_EXTREME_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE * 8,
+ strength = 1000
+ )
return
/obj/singularity/proc/pulse()
for(var/obj/machinery/power/rad_collector/R in GLOB.rad_collectors)
if (get_dist(R, src) <= 15) //Better than using orange() every process.
R.receive_pulse(energy)
+ //Yes, this means rad collectors can double dip on the singulo, but you could always use the safer SM or tesla, so it gets a small buff.
+ radiation_pulse(
+ src,
+ max_range = 15,
+ threshold = RAD_EXTREME_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE * 8,
+ strength = energy * 0.25
+ )
/obj/singularity/proc/on_capture()
chained = 1
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 2ebd159298..ae3d209e99 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -170,12 +170,27 @@
var/turf/TS = get_turf(src) // The turf supermatter is on. SM being in a locker, exosuit, or other container shouldn't block it's effects that way.
if(!istype(TS))
return
+ radiation_pulse(
+ TS,
+ max_range = 255,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DETONATION_RADS,
+ strength = DETONATION_RADS * 5
+ )
var/list/affected_z = GetConnectedZlevels(TS.z)
// Effect 1: Radiation, weakening to all mobs on Z level
- for(var/z in affected_z)
- SSradiation.z_radiate(locate(1, 1, z), DETONATION_RADS, 1)
+ for(var/z_to_affect in affected_z)
+ var/turf/turf_to_hit = locate(TS.x, TS.y, z_to_affect)
+ if(turf_to_hit)
+ radiation_pulse(
+ turf_to_hit,
+ max_range = 255,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = DETONATION_RADS,
+ strength = DETONATION_RADS * 5
+ )
for(var/mob/living/mob in GLOB.living_mob_list)
var/turf/TM = get_turf(mob)
@@ -398,7 +413,16 @@
if(!istype(l.glasses, /obj/item/clothing/glasses/meson) || l.is_incorporeal()) // VOREStation Edit - Only mesons can protect you! OR if they're not in the same plane of existence
l.hallucination = max(0, min(200, l.hallucination + power * config_hallucination_power * sqrt( 1 / max(1,get_dist(l, src)) ) ) )
- SSradiation.radiate(src, max(power * 1.5, 50) ) //Better close those shutters!
+ if(power)
+ // At a power mult of 0.025 for range, this means a 1000power SM (about normal) will reach 25 tiles and be putting off rad pulses of 500. With 0 protection, you have a 10% chance of getting hit.
+ radiation_pulse(
+ src,
+ max_range = CLAMP(max(round(power * 0.025), 3), 3, 50),
+ threshold = CLAMP(RAD_HEAVY_INSULATION - (power * 0.00025), 0.1, 1),
+ chance = round(power * 0.01),
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = round(power * 0.5)
+ )
power -= (power/DECAY_FACTOR)**3 //energy losses due to radiation
@@ -509,8 +533,13 @@
span_warning("The unearthly ringing subsides and you notice you have new radiation burns."), 2)
else
l.show_message(span_warning("You hear an uneartly ringing and notice your skin is covered in fresh radiation burns."), 2)
- var/rads = 500
- SSradiation.radiate(src, rads)
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_HEAVY_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = 200
+ )
/proc/supermatter_pull(var/atom/target, var/pull_range = 255, var/pull_power = STAGE_FIVE)
for(var/atom/A in range(pull_range, target))
@@ -547,18 +576,31 @@
desc = "The shattered remains of a supermatter shard plinth. It doesn't look safe to be around."
icon = 'icons/obj/supermatter.dmi'
icon_state = "darkmatter_broken"
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
/obj/item/broken_sm/Initialize(mapload)
. = ..()
message_admins("Broken SM shard created at ([x],[y],[z] - JMP)")
- START_PROCESSING(SSobj, src)
-/obj/item/broken_sm/process()
- SSradiation.radiate(src, 50)
-
-/obj/item/broken_sm/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
+/obj/item/broken_sm/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 25
+ )
+ last_event = world.time
+ active = FALSE
#undef POWER_FACTOR
#undef DECAY_FACTOR
diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm
index 71c342b392..066a9222f6 100644
--- a/code/modules/projectiles/projectile/arc.dm
+++ b/code/modules/projectiles/projectile/arc.dm
@@ -166,8 +166,14 @@
icon_scale_y = 2
var/rad_power = 50
-/obj/item/projectile/arc/radioactive/on_impact(turf/T)
- SSradiation.radiate(T, rad_power)
+/obj/item/projectile/arc/radioactive/on_impact(turf/T) //This means you can shoot a rad collector to generate power...Might need to be adjusted.
+ radiation_pulse(
+ T,
+ max_range = 3,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ strength = rad_power
+ )
// Blob mortar
/obj/item/projectile/arc/spore
diff --git a/code/modules/radiation/radiation.dm b/code/modules/radiation/radiation.dm
deleted file mode 100644
index b4d0a2d5e2..0000000000
--- a/code/modules/radiation/radiation.dm
+++ /dev/null
@@ -1,73 +0,0 @@
-// Describes a point source of radiation. Created either in response to a pulse of radiation, or over an irradiated atom.
-// Sources will decay over time, unless something is renewing their power!
-/datum/radiation_source
- var/turf/source_turf // Location of the radiation source.
- var/rad_power // Strength of the radiation being emitted.
- var/decay = TRUE // True for automatic decay. False if owner promises to handle it (i.e. supermatter)
- var/respect_maint = FALSE // True for not affecting RAD_SHIELDED areas.
- var/flat = FALSE // True for power falloff with distance.
- var/range // Cached maximum range, used for quick checks against mobs.
-
-/datum/radiation_source/Destroy()
- SSradiation.sources -= src
- if(SSradiation.sources_assoc[src.source_turf] == src)
- SSradiation.sources_assoc -= src.source_turf
- src.source_turf = null
- . = ..()
-
-/datum/radiation_source/proc/update_rad_power(var/new_power = null)
- if(new_power == null || new_power == rad_power)
- return // No change
- else if(new_power <= CONFIG_GET(number/radiation_lower_limit))
- qdel(src) // Decayed to nothing
- else
- rad_power = new_power
- if(rad_power > 1e15) // Arbitrary cap to prevent going to infinity
- rad_power = 1e15
- if(!flat)
- range = min(round(sqrt(rad_power / CONFIG_GET(number/radiation_lower_limit))), 31) // R = rad_power / dist**2 - Solve for dist
- return
-
-/turf
- var/cached_rad_resistance = 0
-
-/turf/proc/calc_rad_resistance()
- cached_rad_resistance = 0
- for(var/obj/O in src.contents)
- if(O.rad_resistance) //Override
- cached_rad_resistance += O.rad_resistance
-
- else if(O.density) //So open doors don't get counted
- var/datum/material/M = O.get_material()
- if(!M) continue
- cached_rad_resistance += (M.weight + M.radiation_resistance) / CONFIG_GET(number/radiation_material_resistance_divisor)
- // Looks like storing the contents length is meant to be a basic check if the cache is stale due to items enter/exiting. Better than nothing so I'm leaving it as is. ~Leshana
- SSradiation.resistance_cache[src] = (length(contents) + 1)
- return
-
-/turf/simulated/wall/calc_rad_resistance()
- SSradiation.resistance_cache[src] = (length(contents) + 1)
- var/temp_rad_resistance
- temp_rad_resistance += material.weight + material.radiation_resistance
- if(reinf_material)
- temp_rad_resistance += reinf_material.weight + reinf_material.radiation_resistance
- cached_rad_resistance = (density ? (temp_rad_resistance) / CONFIG_GET(number/radiation_material_resistance_divisor) : 0)
- return
-
-/turf/simulated/mineral/calc_rad_resistance()
- if(!density)
- return ..()
- SSradiation.resistance_cache[src] = (length(contents) + 1)
- cached_rad_resistance = 60 //Three times that of a steel wall. Rock is less dense than steel, but this is assuming that a normal wall isn't just solid steel all the way through like rock turfs are.
- return
-
-// If people expand the system, this may be useful. Here as a placeholder until then
-/atom/proc/rad_act(var/severity)
- return 1
-
-/mob/living/rad_act(var/severity)
- if(severity && !isbelly(loc)) //eaten mobs are made immune to radiation //VOREStation Edit
- src.apply_effect(severity, IRRADIATE, src.getarmor(null, "rad"))
- for(var/atom/I in src)
- I.rad_act(severity)
- return
diff --git a/code/modules/reagents/holder/holder.dm b/code/modules/reagents/holder/holder.dm
index ccade679db..c8a6e41523 100644
--- a/code/modules/reagents/holder/holder.dm
+++ b/code/modules/reagents/holder/holder.dm
@@ -70,6 +70,8 @@
total_volume = 0
for(var/datum/reagent/R in reagent_list)
if(R.volume < MINIMUM_CHEMICAL_VOLUME)
+ if(isliving(R.holder.my_atom))
+ R.on_mob_end_metabolize(R.holder.my_atom, src)
del_reagent(R.id)
else
total_volume += R.volume
diff --git a/code/modules/reagents/reagents/_reagents.dm b/code/modules/reagents/reagents/_reagents.dm
index 433f360c6c..a8eccfcca8 100644
--- a/code/modules/reagents/reagents/_reagents.dm
+++ b/code/modules/reagents/reagents/_reagents.dm
@@ -46,6 +46,9 @@
var/wiki_flag = 0 // Bitflags for secret/food/drink reagent sorting
var/supply_conversion_value = null
var/industrial_use = null // unique description for export off station
+ /// A list of traits to apply while the reagent is being metabolized.
+ var/list/metabolized_traits
+
var/coolant_modifier = -0.5 // this is multiplied by the volume of the reagent. Most things are not good coolant. EX: Water is 1, coolant is 2. -1 would be a bad reagent for cooling.
@@ -186,6 +189,7 @@
affect_ingest(M, alien, removed * ingest_abs_mult)
if(CHEM_TOUCH)
affect_touch(M, alien, removed)
+ on_mob_metabolize(M, location)
if(overdose && (volume > overdose * M?.species.chemOD_threshold) && (active_metab.metabolism_class != CHEM_TOUCH || can_overdose_touch))
overdose(M, alien, removed)
if(M.species.allergens & allergen_type) //uhoh, we can't handle this!
@@ -241,3 +245,14 @@
/// Called by [/datum/reagents/proc/conditional_update]
/datum/reagent/proc/on_update(atom/A)
return
+
+/datum/reagent/proc/on_mob_metabolize(mob/living/affected_mob, datum/reagents/metabolism/location)
+ SHOULD_CALL_PARENT(TRUE)
+ if(metabolized_traits)
+ affected_mob.add_traits(metabolized_traits, "metabolize_location:[location]reagent:[type]")
+
+/// Called when this reagent stops being metabolized (due to running out)
+/// Has the args 'affected_mob' and 'location' which allows us to remove any traits that is only being added by that reagent holder location. I.e stomach, bloodstream, dermal, etc.
+/datum/reagent/proc/on_mob_end_metabolize(mob/living/affected_mob, datum/reagents/location)
+ SHOULD_CALL_PARENT(TRUE)
+ REMOVE_TRAITS_IN(affected_mob, "metabolize_location:[location]reagent:[type]")
diff --git a/code/modules/reagents/reagents/food_drinks.dm b/code/modules/reagents/reagents/food_drinks.dm
index baadd4c37d..2e512cd9ea 100644
--- a/code/modules/reagents/reagents/food_drinks.dm
+++ b/code/modules/reagents/reagents/food_drinks.dm
@@ -4132,6 +4132,19 @@
allergen_type = ALLERGEN_FRUIT|ALLERGEN_GRAINS //Made from vodka(grains) and orange juice(fruit)
+/datum/reagent/ethanol/screwdrivercocktail/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, metabolization_ratio)
+ . = ..()
+// var/obj/item/organ/internal/liver/liver = drinker.internal_organs_by_name[O_LIVER]
+// if(HAS_TRAIT(liver, TRAIT_ENGINEER_METABOLISM))
+ ADD_TRAIT(drinker, TRAIT_HALT_RADIATION_EFFECTS, "[type]")
+ if (HAS_TRAIT(drinker, TRAIT_IRRADIATED))
+ if(drinker.adjustToxLoss(-2 * metabolization_ratio * seconds_per_tick))
+ return //UPDATE_MOB_HEALTH
+
+/datum/reagent/ethanol/screwdrivercocktail/on_mob_end_metabolize(mob/living/drinker)
+ . = ..()
+ REMOVE_TRAIT(drinker, TRAIT_HALT_RADIATION_EFFECTS, "[type]")
+
/datum/reagent/ethanol/silencer
name = REAGENT_SILENCER
id = REAGENT_ID_SILENCER
diff --git a/code/modules/reagents/reagents/medicine_vr.dm b/code/modules/reagents/reagents/medicine_vr.dm
index 890d9b148d..7c9792470c 100644
--- a/code/modules/reagents/reagents/medicine_vr.dm
+++ b/code/modules/reagents/reagents/medicine_vr.dm
@@ -118,6 +118,7 @@
scannable = SCANNABLE_BENEFICIAL
supply_conversion_value = REFINERYEXPORT_VALUE_COMMON
industrial_use = REFINERYEXPORT_REASON_DRUG
+ metabolized_traits = list(TRAIT_HALT_RADIATION_EFFECTS)
/datum/reagent/prussian_blue/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
if(alien == IS_DIONA)
diff --git a/code/modules/reagents/reagents/toxins.dm b/code/modules/reagents/reagents/toxins.dm
index 7ee11d99c5..2c8b720256 100644
--- a/code/modules/reagents/reagents/toxins.dm
+++ b/code/modules/reagents/reagents/toxins.dm
@@ -1098,7 +1098,7 @@
industrial_use = REFINERYEXPORT_REASON_BIOHAZARD
/datum/reagent/irradiated_nanites/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
- SSradiation.radiate(get_turf(M), 20) // Irradiate people around you.
+ //SSradiation.radiate(get_turf(M), 20) // Irradiate people around you. //TODO
M.radiation = max(M.radiation + 5 * removed, 0) // Irradiate you. Because it's inside you.
/datum/reagent/neurophage_nanites
diff --git a/code/modules/research/tg/designs/modsuit_designs.dm b/code/modules/research/tg/designs/modsuit_designs.dm
index 7e73901e76..e85df6574c 100644
--- a/code/modules/research/tg/designs/modsuit_designs.dm
+++ b/code/modules/research/tg/designs/modsuit_designs.dm
@@ -24,7 +24,7 @@
// Station Suits
/datum/design_techweb/mechfab/modsuit/eva_controller
name = "EVA Suit Control Module"
- desc = "An engineering Hardsuit featuring a visor with welding protection, iommunity to radiation, and insulated gauntlets. It is well insulated against the heat."
+ desc = "An engineering Hardsuit featuring a visor with welding protection, immunity to radiation, and insulated gauntlets. It is well insulated against the heat."
id = "eva_rig_module"
materials = list(MAT_PLASTEEL = 16000, MAT_GOLD = 3000, MAT_GRAPHITE = 4500, MAT_OSMIUM = 1000, MAT_PLASTIC = 4500, MAT_LEAD = 2000, MAT_STEEL = 2000)
build_path = /obj/item/rig/eva
diff --git a/code/modules/xenoarcheaology/effects/radiate.dm b/code/modules/xenoarcheaology/effects/radiate.dm
index 2080b46530..a68301f788 100644
--- a/code/modules/xenoarcheaology/effects/radiate.dm
+++ b/code/modules/xenoarcheaology/effects/radiate.dm
@@ -21,7 +21,14 @@
if(istype(holder, /obj/item/anobattery))
holder = holder.loc
if(holder)
- SSradiation.flat_radiate(holder, radiation_amount, src.effectrange)
+ radiation_pulse(
+ holder,
+ max_range = effectrange,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = chargelevelmax * 0.5,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = chargelevelmax * 0.5
+ )
return 1
/datum/artifact_effect/radiate/DoEffectPulse()
@@ -29,5 +36,11 @@
if(istype(holder, /obj/item/anobattery))
holder = holder.loc
if(holder)
- SSradiation.radiate(holder, ((radiation_amount * 3) * (sqrt(src.effectrange))))
+ radiation_pulse(
+ holder,
+ max_range = effectrange,
+ threshold = RAD_LIGHT_INSULATION,
+ chance = chargelevelmax,
+ strength = chargelevelmax * 2,
+ )
return 1
diff --git a/code/modules/xenobio/items/extracts_vr.dm b/code/modules/xenobio/items/extracts_vr.dm
index 42ae57066f..223e45919a 100644
--- a/code/modules/xenobio/items/extracts_vr.dm
+++ b/code/modules/xenobio/items/extracts_vr.dm
@@ -910,7 +910,35 @@
description_info = "When injected with phoron, this extract creates a single radioactive pulse. When injected with blood, this extract creates a radioactive glob. When injected with water \
this extract creates some radium. When injected with slime jelly, this extract creates some uranium."
slime_type = /mob/living/simple_mob/slime/xenobio/green
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
+/obj/item/slime_extract/green/process()
+ radiate()
+ ..()
+
+/obj/item/slime_extract/green/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 50
+ )
+ last_event = world.time
+ active = FALSE
+
+/obj/item/slime_extract/green/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ . = ..()
/datum/decl/chemical_reaction/instant/slime/green_radpulse
name = "Slime Radiation Pulse"
@@ -924,8 +952,9 @@
playsound(holder.my_atom, 'sound/effects/phasein.ogg', 75, 1)
holder.my_atom.visible_message(span_danger("\The [holder.my_atom] begins to vibrate violently!"))
spawn(5 SECONDS)
- SSradiation.flat_radiate(src, 30, 7, TRUE)
- ..()
+ if(!QDELETED(holder.my_atom) && istype(holder.my_atom, /obj/item/slime_extract/green))
+ START_PROCESSING(SSobj, src)
+
/datum/decl/chemical_reaction/instant/slime/green_emitter
diff --git a/code/modules/xenobio/items/slime_objects.dm b/code/modules/xenobio/items/slime_objects.dm
index 0c95a46925..c1669390fa 100644
--- a/code/modules/xenobio/items/slime_objects.dm
+++ b/code/modules/xenobio/items/slime_objects.dm
@@ -168,6 +168,9 @@
light_power = 0.4
light_range = 2
w_class = ITEMSIZE_TINY
+ var/last_event = 0
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
+ var/active = null
/obj/item/slime_irradiator/Initialize(mapload)
. = ..()
@@ -175,7 +178,25 @@
set_light(light_range, light_power, light_color)
/obj/item/slime_irradiator/process()
- SSradiation.radiate(src, 5)
+ radiate()
+
+/obj/item/slime_irradiator/proc/radiate()
+ SIGNAL_HANDLER
+ if(active)
+ return
+ if(world.time <= last_event + 1.5 SECONDS)
+ return
+ active = TRUE
+ radiation_pulse(
+ src,
+ max_range = 5,
+ threshold = RAD_MEDIUM_INSULATION,
+ chance = URANIUM_IRRADIATION_CHANCE,
+ minimum_exposure_time = URANIUM_RADIATION_MINIMUM_EXPOSURE_TIME,
+ strength = 25
+ )
+ last_event = world.time
+ active = FALSE
/obj/item/slime_irradiator/Destroy()
STOP_PROCESSING(SSobj, src)
diff --git a/config/example/config.txt b/config/example/config.txt
index cb92dad77b..82d227d661 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -58,9 +58,6 @@ BAN_LEGACY_SYSTEM
## Configure how fast explosion strength diminishes when travelling up/down z levels. All explosion distances are multiplied by this each time they go up/down z-levels.
#MULTI_Z_EXPLOSION_SCALAR 0.5
-# Radiation weakens with distance from the source; stop calculating when the strength falls below this value. Lower values mean radiation reaches smaller (with increasingly trivial damage) at the cost of more CPU usage. Max range = DISTANCE^2 * POWER / RADIATION_LOWER_LIMIT
-# RADIATION_LOWER_LIMIT 0.35
-
## disconnect players who did nothing during the set amount of minutes
KICK_INACTIVE 30
@@ -472,24 +469,6 @@ GIVE_FREE_AI_SHELL
## This requires solar controller computers in the map to be set up to use it, with the auto_start variable.
# AUTOSTART_SOLARS
-## Rate of radiation decay (how much it's reduced by) per life tick
-RADIATION_DECAY_RATE 1
-
-## Lower limit on radiation for actually irradiating things on a turf
-RADIATION_LOWER_LIMIT 0.35
-
-## Multiplier for radiation resistances when tracing a ray from source to destination to compute radiation on a turf
-RADIATION_RESISTANCE_MULTIPLIER 8.5
-
-## Divisor for material weights when computing radiation resistance of a material object (walls)
-RADIATION_MATERIAL_RESISTANCE_DIVISOR 1
-
-## Mode of computing radiation resistance into effective radiation on a turf
-## 0:1 subtraction:division for computing effective radiation on a turf
-## 0 / RAD_RESIST_CALC_DIV = Each turf absorbs some fraction of the working radiation level
-## 1 / RAD_RESIST_CALC_SUB = Each turf absorbs a fixed amount of radiation
-RADIATION_RESISTANCE_CALC_MODE 1
-
## IP Reputation Checking
# Enable/disable IP reputation checking (present/nonpresent)
#IP_REPUTATION
diff --git a/icons/mob/inhands/equipment/tools_lefthand.dmi b/icons/mob/inhands/equipment/tools_lefthand.dmi
new file mode 100644
index 0000000000..c8936fde8d
Binary files /dev/null and b/icons/mob/inhands/equipment/tools_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/tools_righthand.dmi b/icons/mob/inhands/equipment/tools_righthand.dmi
new file mode 100644
index 0000000000..192413a739
Binary files /dev/null and b/icons/mob/inhands/equipment/tools_righthand.dmi differ
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 69badf5b83..0ede19366b 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/sound/effects/wounds/blood1.ogg b/sound/effects/wounds/blood1.ogg
new file mode 100644
index 0000000000..62aa1c744b
Binary files /dev/null and b/sound/effects/wounds/blood1.ogg differ
diff --git a/sound/effects/wounds/blood2.ogg b/sound/effects/wounds/blood2.ogg
new file mode 100644
index 0000000000..8c60eaefe1
Binary files /dev/null and b/sound/effects/wounds/blood2.ogg differ
diff --git a/sound/effects/wounds/blood3.ogg b/sound/effects/wounds/blood3.ogg
new file mode 100644
index 0000000000..1427cdfaca
Binary files /dev/null and b/sound/effects/wounds/blood3.ogg differ
diff --git a/sound/effects/wounds/crack1.ogg b/sound/effects/wounds/crack1.ogg
new file mode 100644
index 0000000000..f56c8f8d16
Binary files /dev/null and b/sound/effects/wounds/crack1.ogg differ
diff --git a/sound/effects/wounds/crack2.ogg b/sound/effects/wounds/crack2.ogg
new file mode 100644
index 0000000000..197685f14b
Binary files /dev/null and b/sound/effects/wounds/crack2.ogg differ
diff --git a/sound/effects/wounds/crackandbleed.ogg b/sound/effects/wounds/crackandbleed.ogg
new file mode 100644
index 0000000000..4d8534c7a4
Binary files /dev/null and b/sound/effects/wounds/crackandbleed.ogg differ
diff --git a/sound/effects/wounds/pierce1.ogg b/sound/effects/wounds/pierce1.ogg
new file mode 100644
index 0000000000..56e902307f
Binary files /dev/null and b/sound/effects/wounds/pierce1.ogg differ
diff --git a/sound/effects/wounds/pierce2.ogg b/sound/effects/wounds/pierce2.ogg
new file mode 100644
index 0000000000..627f7b0b50
Binary files /dev/null and b/sound/effects/wounds/pierce2.ogg differ
diff --git a/sound/effects/wounds/pierce3.ogg b/sound/effects/wounds/pierce3.ogg
new file mode 100644
index 0000000000..6256adcac2
Binary files /dev/null and b/sound/effects/wounds/pierce3.ogg differ
diff --git a/sound/effects/wounds/sizzle1.ogg b/sound/effects/wounds/sizzle1.ogg
new file mode 100644
index 0000000000..8490fb2366
Binary files /dev/null and b/sound/effects/wounds/sizzle1.ogg differ
diff --git a/sound/effects/wounds/sizzle2.ogg b/sound/effects/wounds/sizzle2.ogg
new file mode 100644
index 0000000000..95ddfd59c4
Binary files /dev/null and b/sound/effects/wounds/sizzle2.ogg differ
diff --git a/sound/effects/wounds/splatter.ogg b/sound/effects/wounds/splatter.ogg
new file mode 100644
index 0000000000..747d68b396
Binary files /dev/null and b/sound/effects/wounds/splatter.ogg differ
diff --git a/vorestation.dme b/vorestation.dme
index 8fccd75aa0..2b43c0857b 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -134,6 +134,7 @@
#include "code\__defines\movement.dm"
#include "code\__defines\nif.dm"
#include "code\__defines\nifsoft.dm"
+#include "code\__defines\obj_flags.dm"
#include "code\__defines\objects.dm"
#include "code\__defines\observer.dm"
#include "code\__defines\ores.dm"
@@ -157,6 +158,7 @@
#include "code\__defines\projectiles.dm"
#include "code\__defines\pronouns.dm"
#include "code\__defines\qdel.dm"
+#include "code\__defines\radiation.dm"
#include "code\__defines\radio.dm"
#include "code\__defines\recipe.dm"
#include "code\__defines\refinery.dm"
@@ -394,16 +396,19 @@
#include "code\_helpers\icons_procs.dm"
#include "code\_helpers\icons_vr.dm"
#include "code\_helpers\lighting.dm"
+#include "code\_helpers\maths.dm"
#include "code\_helpers\matrices.dm"
#include "code\_helpers\mobs.dm"
#include "code\_helpers\nameof.dm"
#include "code\_helpers\names.dm"
+#include "code\_helpers\radiation.dm"
#include "code\_helpers\refinery.dm"
#include "code\_helpers\roundend.dm"
#include "code\_helpers\roundstats.dm"
#include "code\_helpers\sanitize_values.dm"
#include "code\_helpers\screen_objs.dm"
#include "code\_helpers\shell.dm"
+#include "code\_helpers\spatial_info.dm"
#include "code\_helpers\stack_trace.dm"
#include "code\_helpers\storage.dm"
#include "code\_helpers\string_lists.dm"
@@ -683,10 +688,13 @@
#include "code\datums\components\connect_range.dm"
#include "code\datums\components\deconstructable_research.dm"
#include "code\datums\components\effect_remover.dm"
+#include "code\datums\components\geiger_sound.dm"
#include "code\datums\components\holographic_nature.dm"
#include "code\datums\components\infective.dm"
#include "code\datums\components\orbiter.dm"
#include "code\datums\components\overlay_lighting.dm"
+#include "code\datums\components\radiation_countdown.dm"
+#include "code\datums\components\radioactive_exposure.dm"
#include "code\datums\components\reactive_icon_update.dm"
#include "code\datums\components\recursive_move.dm"
#include "code\datums\components\remote_view.dm"
@@ -852,6 +860,7 @@
#include "code\datums\elements\footstep_override.dm"
#include "code\datums\elements\godmode.dm"
#include "code\datums\elements\light_blocking.dm"
+#include "code\datums\elements\radiation_protected_clothing.dm"
#include "code\datums\elements\rotatable.dm"
#include "code\datums\elements\sellable.dm"
#include "code\datums\elements\slosh.dm"
@@ -4362,7 +4371,6 @@
#include "code\modules\projectiles\targeting\targeting_mob.dm"
#include "code\modules\projectiles\targeting\targeting_overlay.dm"
#include "code\modules\projectiles\targeting\targeting_triggers.dm"
-#include "code\modules\radiation\radiation.dm"
#include "code\modules\random_map\_random_map_setup.dm"
#include "code\modules\random_map\random_map.dm"
#include "code\modules\random_map\random_map_verbs.dm"