diff --git a/code/controllers/Processes/radiation.dm b/code/controllers/Processes/radiation.dm new file mode 100644 index 00000000000..192acf5d5cb --- /dev/null +++ b/code/controllers/Processes/radiation.dm @@ -0,0 +1,56 @@ +/datum/controller/process/radiation + var/repository/radiation/linked = null + +/datum/controller/process/radiation/setup() + name = "radiation controller" + schedule_interval = 20 // every 2 seconds + linked = radiation_repository + +/datum/controller/process/radiation/doWork() + sources_decay() + cache_expires() + irradiate_targets() + +// Step 1 - Sources Decay +/datum/controller/process/radiation/proc/sources_decay() + var/list/sources = linked.sources + for(var/thing in sources) + if(deleted(thing)) + sources.Remove(thing) + continue + var/datum/radiation_source/S = thing + if(S.decay) + S.update_rad_power(S.rad_power - config.radiation_decay_rate) + if(S.rad_power <= config.radiation_lower_limit) + sources.Remove(S) + SCHECK // This scheck probably just wastes resources, but better safe than sorry in this case. + +// Step 2 - Cache Expires +/datum/controller/process/radiation/proc/cache_expires() + var/list/resistance_cache = linked.resistance_cache + for(var/thing in resistance_cache) + if(deleted(thing)) + resistance_cache.Remove(thing) + continue + var/turf/T = thing + if((length(T.contents) + 1) != resistance_cache[T]) + resistance_cache.Remove(T) // If its stale REMOVE it! It will get added if its needed. + SCHECK + + // Step 3 - Registered irradiatable things are checked for radiation +/datum/controller/process/radiation/proc/irradiate_targets() + var/list/registered_listeners = living_mob_list // For now just use this. Nothing else is interested anyway. + if(length(linked.sources) > 0) + for(var/thing in registered_listeners) + if(deleted(thing)) + continue + var/atom/A = thing + var/turf/T = get_turf(thing) + var/rads = linked.get_rads_at_turf(T) + if(rads) + A.rad_act(rads) + SCHECK + +/datum/controller/process/radiation/statProcess() + ..() + stat(null, "[linked.sources.len] sources, [linked.resistance_cache.len] cached turfs") diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 5021453462e..51fc286928c 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -219,6 +219,10 @@ var/list/gamemode_cache = list() var/show_human_death_message = 1 + var/radiation_decay_rate = 1 //How much radiation is reduced by each tick + var/radiation_resistance_multiplier = 6.5 + var/radiation_lower_limit = 0.35 //If the radiation level for a turf would be below this, ignore it. + /datum/configuration/New() var/list/L = typesof(/datum/game_mode) - /datum/game_mode for (var/T in L) @@ -716,6 +720,9 @@ var/list/gamemode_cache = list() if(values.len > 0) language_prefixes = values + if("radiation_lower_limit") + radiation_lower_limit = text2num(value) + else log_misc("Unknown setting in configuration: '[name]'") diff --git a/code/datums/repositories/radiation.dm b/code/datums/repositories/radiation.dm new file mode 100644 index 00000000000..54712820269 --- /dev/null +++ b/code/datums/repositories/radiation.dm @@ -0,0 +1,139 @@ +var/global/repository/radiation/radiation_repository = new() + +/repository/radiation + 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. + +// 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() + radiation_repository.sources -= src + if(radiation_repository.sources_assoc[src.source_turf] == src) + radiation_repository.sources -= src.source_turf + src.source_turf = null + . = ..() + +// TEMPORARY HACK - hard del()'ing sources is too expensive! Until we implement qdel() hints we need to override behavior here +/datum/radiation_source/finalize_qdel() + if(garbage_collector) + garbage_collector.AddTrash(src) + else + delayed_garbage |= src +// TEMPORARY HACK END + +/datum/radiation_source/proc/update_rad_power(var/new_power = null) + if(new_power != null && new_power != rad_power) + rad_power = new_power + . = 1 + if(. && !flat) + range = min(round(sqrt(rad_power / config.radiation_lower_limit)), 31) // R = rad_power / dist**2 - Solve for dist + +// Ray trace from all active radiation sources to T and return the strongest effect. +/repository/radiation/proc/get_rads_at_turf(var/turf/T) + if(!istype(T)) return 0 + + . = 0 + for(var/value in sources) + var/datum/radiation_source/source = value + if(source.rad_power < .) + continue // Already being affected by a stronger source + var/dist = get_dist(source.source_turf, T) + if(dist > source.range) + continue // Too far to possibly affect + if(source.respect_maint) + var/atom/A = T.loc + if(A.flags & RAD_SHIELDED) + continue // In shielded area + 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 + while(origin != T) + origin = get_step_towards(origin, T) //Raytracing + if(!(origin in resistance_cache)) //Only get the resistance if we don't already know it. + origin.calc_rad_resistance() + working = max((working - (origin.cached_rad_resistance * config.radiation_resistance_multiplier)), 0) + if(working <= .) + break // Already affected by a stronger source (or its zero...) + . = max((working * (1 / (dist ** 2))), .) //Butchered version of the inverse square law. Works for this purpose + +// Add a radiation source instance to the repository. It will override any existing source on the same turf. +/repository/radiation/proc/add_source(var/datum/radiation_source/S) + 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 +/repository/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. +/repository/radiation/proc/flat_radiate(source, power, range, var/respect_maint = FALSE) + 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. +/repository/radiation/proc/z_radiate(var/atom/source, power, var/respect_maint = 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_maint) + +/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/material/M = O.get_material() + if(!M) continue + cached_rad_resistance += M.weight + // 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 + radiation_repository.resistance_cache[src] = (length(contents) + 1) + +/turf/simulated/wall/calc_rad_resistance() + radiation_repository.resistance_cache[src] = (length(contents) + 1) + cached_rad_resistance = (density ? material.weight : 0) + +/obj + var/rad_resistance = 0 // Allow overriding rad resistance + +// 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) + src.apply_effect(severity, IRRADIATE, src.getarmor(null, "rad")) + for(var/atom/I in src) + I.rad_act(severity) diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index ad8b89ec804..c5096d12d8b 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -271,8 +271,7 @@ if(explode) explosion(src.loc, devastation_range = 0, heavy_impact_range = 0, light_impact_range = 4, flash_range = 6, adminlog = 0) new /obj/effect/decal/cleanable/greenglow(get_turf(src)) - for(var/mob/living/L in view(10, src)) - L.apply_effect(40, IRRADIATE) + radiation_repository.radiate(src, 50) // This meteor fries toasters. /obj/effect/meteor/emp diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index e78cba35688..5d8d068d49d 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -263,6 +263,7 @@ icon = 'icons/obj/doors/Dooruranium.dmi' mineral = "uranium" var/last_event = 0 + var/rad_power = 7.5 /obj/machinery/door/airlock/process() // Deliberate no call to parent. @@ -280,15 +281,10 @@ /obj/machinery/door/airlock/uranium/process() if(world.time > last_event+20) if(prob(50)) - radiate() + radiation_repository.radiate(src, rad_power) last_event = world.time ..() -/obj/machinery/door/airlock/uranium/proc/radiate() - for(var/mob/living/L in range (3,src)) - L.apply_effect(15,IRRADIATE,0) - return - /obj/machinery/door/airlock/phoron name = "Phoron Airlock" desc = "No way this can end badly." diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm index 3d899377cca..41680d3de76 100644 --- a/code/game/machinery/doors/blast_door.dm +++ b/code/game/machinery/doors/blast_door.dm @@ -13,7 +13,7 @@ icon = 'icons/obj/doors/rapid_pdoor.dmi' icon_state = null min_force = 20 //minimum amount of force needed to damage the door with a melee weapon - + var/material/implicit_material // Icon states for different shutter types. Simply change this instead of rewriting the update_icon proc. var/icon_state_open = null var/icon_state_opening = null @@ -29,6 +29,13 @@ //turning this off prevents awkward zone geometry in places like medbay lobby, for example. block_air_zones = 0 +/obj/machinery/door/blast/initialize() + ..() + implicit_material = get_material_by_name("plasteel") + +/obj/machinery/door/blast/get_material() + return implicit_material + // Proc: Bumped() // Parameters: 1 (AM - Atom that tried to walk through this object) // Description: If we are open returns zero, otherwise returns result of parent function. @@ -46,6 +53,7 @@ icon_state = icon_state_closed else icon_state = icon_state_open + radiation_repository.resistance_cache.Remove(get_turf(src)) return // Proc: force_open() diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 7a1d6abd026..cf7f96d52da 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -362,6 +362,7 @@ icon_state = "door1" else icon_state = "door0" + radiation_repository.resistance_cache.Remove(get_turf(src)) return diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 3d53a053f05..58bed70d075 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -1029,7 +1029,7 @@ icon_state = "engivend" icon_deny = "engivend-deny" req_access = list(access_engine_equip) - products = list(/obj/item/clothing/glasses/meson = 2,/obj/item/device/multitool = 4,/obj/item/weapon/cell/high = 10, + products = list(/obj/item/device/geiger = 4,/obj/item/clothing/glasses/meson = 2,/obj/item/device/multitool = 4,/obj/item/weapon/cell/high = 10, /obj/item/weapon/airlock_electronics = 10,/obj/item/weapon/module/power_control = 10, /obj/item/weapon/circuitboard/airalarm = 10,/obj/item/weapon/circuitboard/firealarm = 10,/obj/item/weapon/circuitboard/status_display = 2, /obj/item/weapon/circuitboard/ai_status_display = 2,/obj/item/weapon/circuitboard/newscaster = 2,/obj/item/weapon/circuitboard/holopad = 2, diff --git a/code/game/mecha/equipment/tools/tools.dm b/code/game/mecha/equipment/tools/tools.dm index 9b2e0aa26dc..4d7ee902d8c 100644 --- a/code/game/mecha/equipment/tools/tools.dm +++ b/code/game/mecha/equipment/tools/tools.dm @@ -973,11 +973,7 @@ process(var/obj/item/mecha_parts/mecha_equipment/generator/nuclear/EG) if(..()) - for(var/mob/living/carbon/M in view(EG.chassis)) - if(istype(M,/mob/living/carbon/human)) - M.apply_effect((EG.rad_per_cycle*3),IRRADIATE,0) - else - M.apply_effect(EG.rad_per_cycle, IRRADIATE) + radiation_repository.radiate(EG, (EG.rad_per_cycle * 3)) return 1 diff --git a/code/game/objects/items/devices/geiger.dm b/code/game/objects/items/devices/geiger.dm new file mode 100644 index 00000000000..62cb9d9862c --- /dev/null +++ b/code/game/objects/items/devices/geiger.dm @@ -0,0 +1,53 @@ +#define RAD_LEVEL_LOW 5 //10 // Around the level at which radiation starts to become harmful +#define RAD_LEVEL_MODERATE 15 //15 +#define RAD_LEVEL_HIGH 50 //50 +#define RAD_LEVEL_VERY_HIGH 100 //100 + +//Geiger counter +//Rewritten version of TG's geiger counter +//I opted to show exact radiation levels + +/obj/item/device/geiger + name = "geiger counter" + desc = "A handheld device used for detecting and measuring radiation in an area." + icon_state = "geiger_off" + item_state = "multitool" + w_class = ITEMSIZE_SMALL + var/scanning = 0 + var/radiation_count = 0 + +/obj/item/device/geiger/New() + processing_objects |= src + +/obj/item/device/geiger/process() + if(!scanning) + return + radiation_count = radiation_repository.get_rads_at_turf(get_turf(src)) + update_icon() + +/obj/item/device/geiger/examine(mob/user) + ..(user) + to_chat(user, "[scanning ? "ambient" : "stored"] radiation level: [radiation_count ? radiation_count : "0"]Bq.") + +/obj/item/device/geiger/attack_self(var/mob/user) + scanning = !scanning + update_icon() + to_chat(user, "\icon[src] You switch [scanning ? "on" : "off"] [src].") + +/obj/item/device/geiger/update_icon() + if(!scanning) + icon_state = "geiger_off" + return 1 + + switch(radiation_count) + if(null) icon_state = "geiger_on_1" + if(-INFINITY to RAD_LEVEL_LOW) icon_state = "geiger_on_1" + if(RAD_LEVEL_LOW + 1 to RAD_LEVEL_MODERATE) icon_state = "geiger_on_2" + if(RAD_LEVEL_MODERATE + 1 to RAD_LEVEL_HIGH) icon_state = "geiger_on_3" + if(RAD_LEVEL_HIGH + 1 to RAD_LEVEL_VERY_HIGH) icon_state = "geiger_on_4" + if(RAD_LEVEL_VERY_HIGH + 1 to INFINITY) icon_state = "geiger_on_5" + +#undef RAD_LEVEL_LOW +#undef RAD_LEVEL_MODERATE +#undef RAD_LEVEL_HIGH +#undef RAD_LEVEL_VERY_HIGH \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 5d883784aa0..cfd0693b15d 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -99,6 +99,8 @@ REAGENT SCANNER if(M.status_flags & FAKEDEATH) OX = fake_oxy > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" user.show_message("[OX] | [TX] | [BU] | [BR]") + if(M.radiation) + user.show_message("Radiation detected.") if(istype(M, /mob/living/carbon)) var/mob/living/carbon/C = M if(C.reagents.total_volume) diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm index 5c8aa2b2e02..67a4d3e788e 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -155,6 +155,8 @@ new /obj/item/clothing/head/radiation(src) new /obj/item/clothing/suit/radiation(src) new /obj/item/clothing/head/radiation(src) + new /obj/item/device/geiger(src) + new /obj/item/device/geiger(src) /* * Bombsuit closet diff --git a/code/game/objects/structures/simple_doors.dm b/code/game/objects/structures/simple_doors.dm index 58742c3b609..4e302e0e317 100644 --- a/code/game/objects/structures/simple_doors.dm +++ b/code/game/objects/structures/simple_doors.dm @@ -168,8 +168,7 @@ /obj/structure/simple_door/process() if(!material.radioactivity) return - for(var/mob/living/L in range(1,src)) - L.apply_effect(round(material.radioactivity/3),IRRADIATE,0) + radiation_repository.radiate(src, round(material.radioactivity/3)) /obj/structure/simple_door/iron/New(var/newloc,var/material_name) ..(newloc, "iron") diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm index 098782ebdf0..890574d6641 100644 --- a/code/game/turfs/simulated/wall_attacks.dm +++ b/code/game/turfs/simulated/wall_attacks.dm @@ -7,6 +7,8 @@ if(can_open == WALL_OPENING) return + radiation_repository.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 dc7874b2aaf..eab99bfba72 100644 --- a/code/game/turfs/simulated/wall_icon.dm +++ b/code/game/turfs/simulated/wall_icon.dm @@ -26,6 +26,7 @@ else if(material.opacity < 0.5 && opacity) set_light(0) + radiation_repository.resistance_cache.Remove(src) update_connections(1) update_icon() diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index f8544e714d4..376fb8e452a 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -48,6 +48,9 @@ if(!radiate()) return PROCESS_KILL +/turf/simulated/wall/proc/get_material() + return material + /turf/simulated/wall/bullet_act(var/obj/item/projectile/Proj) if(istype(Proj,/obj/item/projectile/beam)) burn(2500) @@ -265,8 +268,7 @@ if(!total_radiation) return - for(var/mob/living/L in range(3,src)) - L.apply_effect(total_radiation, IRRADIATE,0) + radiation_repository.radiate(src, total_radiation) return total_radiation /turf/simulated/wall/proc/burn(temperature) diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm index b9435f25aa4..bf2dec5e4e5 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -2,7 +2,7 @@ var/const/enterBelt = 30 var/const/radIntervall = 5 // Enough time between enter/leave belt for 10 hits, as per original implementation var/const/leaveBelt = 80 - var/const/revokeAccess = 135 + var/const/revokeAccess = 165 startWhen = 2 announceWhen = 1 endWhen = revokeAccess @@ -27,23 +27,21 @@ radiate() else if(activeFor == leaveBelt) - command_announcement.Announce("The station has passed the radiation belt. Please report to medbay if you experience any unusual symptoms. Maintenance will lose all access again shortly.", "Anomaly Alert") - + command_announcement.Announce("The station 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() + var/radiation_level = rand(15, 35) + for(var/z in using_map.station_levels) + radiation_repository.z_radiate(locate(1, 1, z), radiation_level, 1) + for(var/mob/living/carbon/C in living_mob_list) var/area/A = get_area(C) if(!A) continue - if(!(A.z in using_map.station_levels)) - continue if(A.flags & RAD_SHIELDED) continue - if(istype(C,/mob/living/carbon/human)) var/mob/living/carbon/human/H = C - H.apply_effect((rand(15,35)),IRRADIATE,0) if(prob(5)) - H.apply_effect((rand(40,70)),IRRADIATE,0) if (prob(75)) randmutb(H) // Applies bad mutation domutcheck(H,null,MUTCHK_FORCED) diff --git a/code/modules/events/solar_storm.dm b/code/modules/events/solar_storm.dm index 971e0418bc9..afaa01ed95a 100644 --- a/code/modules/events/solar_storm.dm +++ b/code/modules/events/solar_storm.dm @@ -27,6 +27,7 @@ radiate() /datum/event/solar_storm/proc/radiate() + // Note: Too complicated to be worth trying to use the radiation system for this. Its only in space anyway, so we make an exception in this case. for(var/mob/living/L in living_mob_list) var/turf/T = get_turf(L) if(!T) @@ -36,8 +37,7 @@ continue //Todo: Apply some burn damage from the heat of the sun. Until then, enjoy some moderate radiation. - L.apply_effect((rand(15,30)),IRRADIATE,blocked = L.getarmor(null, "rad")) - + L.rad_act(rand(15, 30)) /datum/event/solar_storm/end() command_announcement.Announce("The solar storm has passed the station. 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/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index bf59f3c3826..a6a857c43b8 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -482,7 +482,7 @@ var/list/mining_overlay_cache = list() M.flash_eyes() if(prob(50)) M.Stun(5) - M.apply_effect(25, IRRADIATE) + radiation_repository.flat_radiate(src, 25, 200) if(prob(25)) excavate_find(prob(5), finds[1]) else if(rand(1,500) == 1) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 61b13316b84..8f7398e0a86 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -94,7 +94,7 @@ emp_act var/obj/item/organ/external/organ = organs_by_name[organ_name] if(organ) var/weight = organ_rel_size[organ_name] - armorval += getarmor_organ(organ, type) * weight + armorval += (getarmor_organ(organ, type) * weight) total += weight return (armorval/max(total, 1)) diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 795e535ebfd..4b2c7f6f79b 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -352,6 +352,7 @@ var/global/list/robot_modules = list( src.modules += new /obj/item/device/pipe_painter(src) src.modules += new /obj/item/device/floor_painter(src) src.modules += new /obj/item/weapon/gripper/no_use/loader(src) + src.modules += new /obj/item/device/geiger(src) var/datum/matter_synth/metal = new /datum/matter_synth/metal() var/datum/matter_synth/plasteel = new /datum/matter_synth/plasteel() @@ -396,6 +397,7 @@ var/global/list/robot_modules = list( src.modules += new /obj/item/device/pipe_painter(src) src.modules += new /obj/item/device/floor_painter(src) src.emag = new /obj/item/weapon/melee/baton/robot/arm(src) + src.modules += new /obj/item/device/geiger(src) var/datum/matter_synth/metal = new /datum/matter_synth/metal(40000) var/datum/matter_synth/glass = new /datum/matter_synth/glass(40000) diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index f900f6fcf45..6a40cf3b8ea 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -396,17 +396,13 @@ /obj/machinery/power/port_gen/pacman/super/UseFuel() //produces a tiny amount of radiation when in use if (prob(2*power_output)) - for (var/mob/living/L in range(src, 5)) - L.apply_effect(1, IRRADIATE) //should amount to ~5 rads per minute at max safe power + radiation_repository.radiate(src, 4) ..() /obj/machinery/power/port_gen/pacman/super/explode() //a nice burst of radiation var/rads = 50 + (sheets + sheet_left)*1.5 - for (var/mob/living/L in range(src, 10)) - //should really fall with the square of the distance, but that makes the rads value drop too fast - //I dunno, maybe physics works different when you live in 2D -- SM radiation also works like this, apparently - L.apply_effect(max(20, round(rads/get_dist(L,src))), IRRADIATE) + radiation_repository.radiate(src, (max(20, rads))) explosion(src.loc, 3, 3, 5, 3) qdel(src) diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm index dee15d9f140..47a9c08923e 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -31,6 +31,11 @@ var/global/list/rad_collectors = list() last_power_new = 0 + if(P && active) + var/rads = radiation_repository.get_rads_at_turf(get_turf(src)) + if(rads) + receive_pulse(rads * 5) //Maths is hard + if(P) if(P.air_contents.gas["phoron"] == 0) investigate_log("out of fuel.","singulo") diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 39090ceac50..9309c6bd3cc 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -406,15 +406,13 @@ var/toxrange = 10 var/toxdamage = 4 var/radiation = 15 - var/radiationmin = 3 if (src.energy>200) toxdamage = round(((src.energy-150)/50)*4,1) radiation = round(((src.energy-150)/50)*5,1) - radiationmin = round((radiation/5),1)// + radiation_repository.radiate(src, radiation) //Always radiate at max, so a decent dose of radiation is applied for(var/mob/living/M in view(toxrange, src.loc)) if(M.status_flags & GODMODE) continue - M.apply_effect(rand(radiationmin,radiation), IRRADIATE) toxdamage = (toxdamage - (toxdamage*M.getarmor(null, "rad"))) M.apply_effect(toxdamage, TOX) return @@ -448,13 +446,13 @@ /obj/singularity/proc/smwave() for(var/mob/living/M in view(10, src.loc)) if(prob(67)) - M.apply_effect(rand(energy), IRRADIATE) - M << "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat." - M << "Miraculously, it fails to kill you." + to_chat(M, "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat.") + to_chat(M, "Miraculously, it fails to kill you.") else M << "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat." M << "You don't even have a moment to react as you are reduced to ashes by the intense radiation." M.dust() + radiation_repository.radiate(src, rand(energy)) return /obj/singularity/proc/pulse() diff --git a/code/modules/supermatter/supermatter.dm b/code/modules/supermatter/supermatter.dm index 1ebd8207e06..046c315ec98 100644 --- a/code/modules/supermatter/supermatter.dm +++ b/code/modules/supermatter/supermatter.dm @@ -23,8 +23,11 @@ #define DAMAGE_RATE_LIMIT 3 //damage rate cap at power = 300, scales linearly with power -//These would be what you would get at point blank, decreases with distance -#define DETONATION_RADS 200 +// Base variants are applied to everyone on the same Z level +// Range variants are applied on per-range basis: numbers here are on point blank, it scales with the map size (assumes square shaped Z levels) +#define DETONATION_RADS 20 +#define DETONATION_HALLUCINATION_BASE 300 +#define DETONATION_HALLUCINATION_RANGE 300 #define DETONATION_HALLUCINATION 600 @@ -95,6 +98,11 @@ anchored = 1 grav_pulling = 1 exploded = 1 + var/turf/TS = get_turf(src) // The turf supermatter is on. SM being in a locker, mecha, or other container shouldn't block it's effects that way. + if(!TS) + return + for(var/z in GetConnectedZlevels(TS.z)) + radiation_repository.z_radiate(locate(1, 1, z), DETONATION_RADS, 1) for(var/mob/living/mob in living_mob_list) var/turf/T = get_turf(mob) if(T && (loc.z == T.z)) @@ -102,8 +110,6 @@ //Hilariously enough, running into a closet should make you get hit the hardest. var/mob/living/carbon/human/H = mob H.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(mob, src) + 1)) ) ) - var/rads = DETONATION_RADS * sqrt( 1 / (get_dist(mob, src) + 1) ) - mob.apply_effect(rads, IRRADIATE) spawn(pull_time) explosion(get_turf(src), explosion_power, explosion_power * 2, explosion_power * 3, explosion_power * 4, 1) qdel(src) @@ -258,12 +264,15 @@ if(eye_shield < 1) l.hallucination = max(0, min(200, l.hallucination + power * config_hallucination_power * sqrt( 1 / max(1,get_dist(l, src)) ) ) ) +/* //adjusted range so that a power of 170 (pretty high) results in 9 tiles, roughly the distance from the core to the engine monitoring room. //note that the rads given at the maximum range is a constant 0.2 - as power increases the maximum range merely increases. for(var/mob/living/l in range(src, round(sqrt(power / 2)))) var/radius = max(get_dist(l, src), 1) var/rads = (power / 10) * ( 1 / (radius**2) ) l.apply_effect(rads, IRRADIATE) +*/ + radiation_repository.radiate(src, power * 1.5) //Better close those shutters! power -= (power/DECAY_FACTOR)**3 //energy losses due to radiation @@ -377,9 +386,8 @@ "The unearthly ringing subsides and you notice you have new radiation burns.", 2) else l.show_message("You hear an uneartly ringing and notice your skin is covered in fresh radiation burns.", 2) - var/rads = 500 * sqrt( 1 / (get_dist(l, src) + 1) ) - l.apply_effect(rads, IRRADIATE) - + var/rads = 500 + radiation_repository.radiate(src, rads) /obj/machinery/power/supermatter/proc/supermatter_pull() //following is adapted from singulo code diff --git a/code/modules/xenoarcheaology/effects/radiate.dm b/code/modules/xenoarcheaology/effects/radiate.dm index 78164097573..6c8d37838a7 100644 --- a/code/modules/xenoarcheaology/effects/radiate.dm +++ b/code/modules/xenoarcheaology/effects/radiate.dm @@ -15,16 +15,10 @@ /datum/artifact_effect/radiate/DoEffectAura() if(holder) - var/turf/T = get_turf(holder) - for (var/mob/living/M in range(src.effectrange,T)) - M.apply_effect(radiation_amount,IRRADIATE,0) - M.updatehealth() + radiation_repository.flat_radiate(holder, radiation_amount, src.effectrange) return 1 /datum/artifact_effect/radiate/DoEffectPulse() if(holder) - var/turf/T = get_turf(holder) - for (var/mob/living/M in range(src.effectrange,T)) - M.apply_effect(radiation_amount * 25,IRRADIATE,0) - M.updatehealth() + radiation_repository.radiate(holder, ((radiation_amount * 25) * (sqrt(src.effectrange)))) //Need to get feedback on this return 1 diff --git a/code/modules/xenoarcheaology/tools/geosample_scanner.dm b/code/modules/xenoarcheaology/tools/geosample_scanner.dm index 9d6450a839b..9e3343b6802 100644 --- a/code/modules/xenoarcheaology/tools/geosample_scanner.dm +++ b/code/modules/xenoarcheaology/tools/geosample_scanner.dm @@ -198,8 +198,7 @@ radiation = rand() * 15 + 85 if(!rad_shield) //irradiate nearby mobs - for(var/mob/living/M in view(7,src)) - M.apply_effect(radiation / 25, IRRADIATE, 0) + radiation_repository.radiate(src, radiation / 25) else t_left_radspike = pick(10,15,25) @@ -361,4 +360,4 @@ scanned_item = null add_fingerprint(usr) - return 1 // update UIs attached to this object + return 1 // update UIs attached to this object \ No newline at end of file diff --git a/config/example/config.txt b/config/example/config.txt index 232d3c66ad1..52892bc14a3 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -36,6 +36,9 @@ JOBS_HAVE_MINIMAL_ACCESS 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 + ## log OOC channel LOG_OOC diff --git a/html/changelogs/Leshana-Radiation.yml b/html/changelogs/Leshana-Radiation.yml new file mode 100644 index 00000000000..d4d9c3589d7 --- /dev/null +++ b/html/changelogs/Leshana-Radiation.yml @@ -0,0 +1,5 @@ +author: Leshana +delete-after: True +changes: + - tweak: "Optimized the unified radiation system. Made the radiation cutoff level configurable." + - bugfix: "Standing still won't save you from radiation storms." diff --git a/html/changelogs/cirra-Radiation.yml b/html/changelogs/cirra-Radiation.yml new file mode 100644 index 00000000000..53038532cde --- /dev/null +++ b/html/changelogs/cirra-Radiation.yml @@ -0,0 +1,37 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Cirra + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added a unified radiation system. Radiation is lessened by obstacles, and distance." + - rscadd: "Added a geiger counter for measuring radiation levels, which can be found in certain vending machines and radiation closets." diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 336f8dfe9ca..1198dc03144 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/polaris.dme b/polaris.dme index 92abfde4f5d..3d82655c4e9 100644 --- a/polaris.dme +++ b/polaris.dme @@ -151,6 +151,7 @@ #include "code\controllers\Processes\nanoui.dm" #include "code\controllers\Processes\obj.dm" #include "code\controllers\Processes\planet.dm" +#include "code\controllers\Processes\radiation.dm" #include "code\controllers\Processes\scheduler.dm" #include "code\controllers\Processes\Shuttle.dm" #include "code\controllers\Processes\sun.dm" @@ -213,6 +214,7 @@ #include "code\datums\repositories\cameras.dm" #include "code\datums\repositories\crew.dm" #include "code\datums\repositories\decls.dm" +#include "code\datums\repositories\radiation.dm" #include "code\datums\repositories\repository.dm" #include "code\datums\supplypacks\atmospherics.dm" #include "code\datums\supplypacks\contraband.dm" @@ -769,6 +771,7 @@ #include "code\game\objects\items\devices\flash.dm" #include "code\game\objects\items\devices\flashlight.dm" #include "code\game\objects\items\devices\floor_painter.dm" +#include "code\game\objects\items\devices\geiger.dm" #include "code\game\objects\items\devices\hacktool.dm" #include "code\game\objects\items\devices\lightreplacer.dm" #include "code\game\objects\items\devices\locker_painter.dm"