Radiation Refactor (#19270)

* Part 1

* WIP

* The rest of these

* More stuff

* Whoops, did that wrong

* typo

* gweeen

* This all works

* SHOWER

* Rads

* awa

* rad

* Update life.dm

* edits

* Makes lvl 3 rads give you a warning.

You should already know by this point, but this makes it EXTRA clear you're getting fucked

* Update vorestation.dme

* aaa

* propagate

* gwah

* more fixes

* AAA

* Update radiation.dm

* Update radiation.dm

* mobs rads

* rads

* fix this

* Update _reagents.dm

* these

* Get rid of these

* rad

* Update config.txt

* fixed

* Update radiation_effects.dm
This commit is contained in:
Cameron Lennox
2026-03-22 12:29:09 -04:00
committed by GitHub
parent d91baf9c9f
commit cbc4151bfb
113 changed files with 2044 additions and 551 deletions
+92
View File
@@ -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
@@ -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
@@ -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"
@@ -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)