Files
FenodyreeAv 33b8164013 Radiation Damage Rebalance (#22238)
Adds: 
- Dosimeter, keeps track of how many rads a person has absorbed as well
as showing the dose after armour.
- Adds a message to shelter in maint to the supermatter explosion.
- Adds a flag that makes damage not apply to robotic limbs, useful for
preventing radiation burns to metal parts. Should also be added to
chemicals that deal brute damage, such as arithrazine and the the burns
from chemical exposure to chlorine, but that's another PR.
- Added radiation resistance values to makeshift armour. As it only
covers the chest, this isn't very useful, but better than nothing.

Balance:

- Re-scaled radiation damage.
- Radiation now maxes out at 1000, from the previous 100.
- Radiation damage when at maximum rads is converted directly into burn
damage.
- Radiation sources decay slowly, so all the old numbers were maxing
people's radiation values instantaneously.
- Re-scaled organ damage from radiation. Previously you would take no
organ damage till 75% rads, then lose your liver almost immediately. Now
organ damage ramps up from 500-1000, with 750-1000 being about 1/3rd the
organ damage of 75-100.
- Added debilitating, but not lethal, effects to moderate and high
radiation levels. Lots of pain events instead of straight damage.
- Reduced speed and stamina for high radiation doses.
- Hyronalin and Arithrazine both remove much more radiation.


The intent: People should now be able to work in low radiation zones,
without using the radiation suit for short periods of time.

The geiger counter is no longer an 'if this is ticking you are at max
rads' detector.

With the engineering voidsuit, 75% rads, I could stand next to a charged
supermatter ~50rads a second, for about 2 minutes before I hit lethal
radiation levels.

If people stay in a radiation zone, or are exposed to extreme radiation
100+ rads a second, radiation is much more lethal, but not by just
killing your organs. Conventional medicine will help a lot.

---------

Signed-off-by: FenodyreeAv <fenodyree.av@gmail.com>
Co-authored-by: Batrachophreno <Batrochophreno@gmail.com>
2026-04-21 12:43:25 +00:00

160 lines
4.9 KiB
Plaintext

SUBSYSTEM_DEF(radiation)
name = "Radiation"
wait = 2 SECONDS
priority = SS_PRIORITY_RADIATION
flags = SS_NO_INIT
/// All radiation source datums.
var/list/sources = list()
/// Sources indexed by turf for de-duplication.
var/list/sources_assoc = list()
/// Cache of turf's radiation resistance.
var/list/resistance_cache = list()
var/list/current_sources = list()
var/list/current_res_cache = list()
var/list/listeners = 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()
while(length(current_sources))
var/datum/radiation_source/S = current_sources[length(current_sources)]
LIST_DEC(current_sources)
if(QDELETED(S))
sources -= S
else if(S.decay)
S.update_rad_power(S.rad_power - (RADIATION_DECAY_RATE + S.accelerated_decay_rate))
if (MC_TICK_CHECK)
return
while(length(current_res_cache))
var/turf/T = current_res_cache[length(current_res_cache)]
LIST_DEC(current_res_cache)
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)]
LIST_DEC(listeners)
if(!QDELETED(A))
var/atom/location = A.loc
var/rads = 0
if(istype(location))
rads = location.get_rads()
if(rads)
A.rad_act(rads)
if (MC_TICK_CHECK)
return
/**
* Ray trace from all active radiation sources to T and return the strongest effect.
*/
/datum/controller/subsystem/radiation/proc/get_rads_at_turf(turf/T)
. = 0
if(!istype(T))
return
for(var/value in sources)
var/datum/radiation_source/source = value
if(source.rad_power < .)
continue // Already being affected by a stronger source
if(source.source_turf.z != T.z)
continue // Radiation is not multi-z
if(source.respect_rad_shielding)
var/area/A = T.loc
if(A.area_flags & AREA_FLAG_RAD_SHIELDED)
continue // In shielded area
var/dist = get_dist(source.source_turf, T)
if(dist > source.range)
continue // Too far to possibly affect
if(source.flat)
. = max(., source.rad_power)
continue // No need to ray trace for flat field
// Okay, now ray trace to find resistence!
var/turf/origin = source.source_turf
var/working = source.rad_power
// Ray tracing.
while(origin != T)
origin = get_step_towards(origin, T)
// Only get the resistance if we don't already know it.
if(!resistance_cache[origin])
origin.calc_rad_resistance()
if(origin.cached_rad_resistance)
working = round((working / (origin.cached_rad_resistance * RADIATION_RESISTANCE_MULTIPLIER)), 0.1)
// Already affected by a stronger source (or its zero...)
if((working <= .) || (working <= RADIATION_THRESHOLD_CUTOFF))
break
// Butchered version of the inverse square law. Works for this purpose.
. = max((working / (dist ** 2)), .)
if(. <= RADIATION_THRESHOLD_CUTOFF)
. = 0
/**
* Add a /datum/radiation_source instance to the repository. It will override any existing source on the same turf.
*/
/datum/controller/subsystem/radiation/proc/add_source(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 /datum/radiation_source that will decay over time.
*
* This source will send out regular radiation pulses that take walls and distance into account.
*/
/datum/controller/subsystem/radiation/proc/radiate(source, power, increased_decay)
if(!(source && power)) //Sanity checking
return
var/datum/radiation_source/S = new()
S.source_turf = get_turf(source)
S.update_rad_power(power)
S.accelerated_decay_rate = increased_decay
add_source(S)
/**
* Creates a /datum/radiation_source that will decay over time.
*
* This source will send out regular radiation pulses that will deliver
* identical radiation doses to everyone in range, irrespective of barriers
* in the way or their distance from the source.
*/
/datum/controller/subsystem/radiation/proc/flat_radiate(source, power, range, respect_rad_shielding = FALSE)
if(!(source && power && range))
return
var/datum/radiation_source/S = new()
S.flat = TRUE
S.range = range
S.respect_rad_shielding = respect_rad_shielding
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(atom/source, power, respect_rad_shielding = FALSE)
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_rad_shielding)