diff --git a/code/__defines/subsystem-priority.dm b/code/__defines/subsystem-priority.dm
index df4248e2462b0..14444079151ad 100644
--- a/code/__defines/subsystem-priority.dm
+++ b/code/__defines/subsystem-priority.dm
@@ -44,6 +44,7 @@
#define SS_PRIORITY_PROCESSING 95 // Generic datum processor. Replaces objects processor.
#define SS_PRIORITY_PLANTS 90 // Plant processing, slow ticks.
#define SS_PRIORITY_VINES 50 // Spreading vine effects.
+#define SS_PRIORITY_EXPLOSIVES 40 // Explosion processing.
#define SS_PRIORITY_PSYCHICS 45 // Psychic complexus processing.
#define SS_PRIORITY_NANO 40 // Updates to nanoui uis.
#define SS_PRIORITY_TURF 30 // Radioactive walls/blob.
diff --git a/code/__defines/turfs.dm b/code/__defines/turfs.dm
index c6cf13fbb28b8..c7b376bc1cfb6 100644
--- a/code/__defines/turfs.dm
+++ b/code/__defines/turfs.dm
@@ -20,3 +20,13 @@
#define SMOOTH_BLACKLIST 3 //Smooth with all but a blacklist of subtypes
#define RANGE_TURFS(CENTER, RADIUS) 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))
+
+
+#define RANGED_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) \
+ )
diff --git a/code/_global_vars/misc.dm b/code/_global_vars/misc.dm
index 45247b7a67374..67e68e43ede00 100644
--- a/code/_global_vars/misc.dm
+++ b/code/_global_vars/misc.dm
@@ -1,7 +1,7 @@
GLOBAL_LIST_EMPTY(all_observable_events)
// True if net rebuild will be called manually after an event.
-GLOBAL_VAR_INIT(defer_powernet_rebuild, FALSE)
+//GLOBAL_VAR_INIT(defer_powernet_rebuild, FALSE) //[SIERRA-REMOVE]
// Those networks can only be accessed by pre-existing terminals. AIs and new terminals can't use them.
GLOBAL_LIST_INIT(restricted_camera_networks, list(\
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 0d02d8324a6aa..119d4383e9906 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -449,6 +449,11 @@
// [SIERRA-ADD]
var/static/shutdown_on_reboot = FALSE
+ var/static/use_spreading_explosions = TRUE //Defines whether the server uses iterative or circular explosions.
+
+ var/static/iterative_explosives_z_threshold = 15
+ var/static/iterative_explosives_z_multiplier = 0.25
+ var/static/iterative_explosives_z_subtraction = 4
// [/SIERRA-ADD]
@@ -898,6 +903,18 @@
// [SIERRA-ADD]
if ("shutdown_on_reboot")
shutdown_on_reboot = TRUE
+
+ if ("explosion_z_threshold")
+ iterative_explosives_z_threshold = text2num(value)
+
+ if ("explosion_z_mult")
+ iterative_explosives_z_multiplier = text2num(value)
+
+ if ("explosion_z_sub")
+ iterative_explosives_z_subtraction = text2num(value)
+
+ if ("use_spreading_explosions")
+ use_spreading_explosions = TRUE
// [/SIERRA-ADD]
// [SIERRA-ADD] - EX666_ECOSYSTEM
if ("overflow_server_url")
diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm
index a79331aa8c137..5420c7e6c15b1 100644
--- a/code/controllers/controller.dm
+++ b/code/controllers/controller.dm
@@ -51,3 +51,11 @@
return TRUE
statNext = time + 1 SECONDS
return FALSE
+
+//[SIERRA-ADD]
+// Called when SSexplosives begins processing explosions.
+/datum/controller/proc/ExplosionStart()
+
+// Called when SSexplosives finishes processing all queued explosions.
+/datum/controller/proc/ExplosionEnd()
+//[/SIERRA-ADD]
diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm
index bc8dc540b9c9c..dcafdc872845c 100644
--- a/code/game/objects/explosion.dm
+++ b/code/game/objects/explosion.dm
@@ -1,108 +1,51 @@
-#define EXPLOSION_RATIO_DEVASTATION 1
-#define EXPLOSION_RATIO_HEAVY 2
-#define EXPLOSION_RATIO_LIGHT 4
+// explosion logic is in code/controllers/Processes/explosives.dm now
-
-/proc/explosion(turf/epicenter, range, max_power = EX_ACT_DEVASTATING, adminlog = 1, z_transfer = UP|DOWN, shaped, turf_breaker)
- set waitfor = FALSE
-
- var/multi_z_scalar = 0.35
+/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, z_transfer = UP|DOWN, shaped, turf_breaker, spreading = config.use_spreading_explosions)
UNLINT(src = null) //so we don't abort once src is deleted
- epicenter = get_turf(epicenter)
- if(!epicenter) return
-
- /// Range, in tiles, of `EX_ACT_DEVASTATING` damage.
- var/devastation_range = 0
- /// Range, in tiles, of `EX_ACT_HEAVY` damage.
- var/heavy_impact_range = 0
- /// Range, in tiles, of `EX_ACT_LIGHT` damage.
- var/light_impact_range = 0
- /// Ratio multiplier based on `max_power` and `range` used to determine the above three range values.
- var/explosion_ratio = 0
-
- switch (max_power)
- if (EX_ACT_DEVASTATING)
- explosion_ratio = range / (EXPLOSION_RATIO_DEVASTATION + EXPLOSION_RATIO_HEAVY + EXPLOSION_RATIO_LIGHT)
- devastation_range = round(explosion_ratio * EXPLOSION_RATIO_DEVASTATION)
- heavy_impact_range = round(explosion_ratio * EXPLOSION_RATIO_HEAVY)
- light_impact_range = round(explosion_ratio * EXPLOSION_RATIO_LIGHT)
- if (EX_ACT_HEAVY)
- explosion_ratio = range / (EXPLOSION_RATIO_HEAVY + EXPLOSION_RATIO_LIGHT)
- heavy_impact_range = round(explosion_ratio * EXPLOSION_RATIO_HEAVY)
- light_impact_range = round(explosion_ratio * EXPLOSION_RATIO_LIGHT)
- if (EX_ACT_LIGHT)
- explosion_ratio = range
- light_impact_range = range
-
- // Handles recursive propagation of explosions.
- if(z_transfer)
- var/adj_dev = max(0, (multi_z_scalar * devastation_range) - (shaped ? 2 : 0) )
- var/adj_heavy = max(0, (multi_z_scalar * heavy_impact_range) - (shaped ? 2 : 0) )
- var/adj_light = max(0, (multi_z_scalar * light_impact_range) - (shaped ? 2 : 0) )
- var/adj_range = adj_dev + adj_heavy + adj_light
-
- var/adj_max_power
- if (adj_dev)
- adj_max_power = EX_ACT_DEVASTATING
- else if (adj_heavy)
- adj_max_power = EX_ACT_HEAVY
- else
- adj_max_power = EX_ACT_LIGHT
-
- if(adj_dev > 0 || adj_heavy > 0)
- if(HasAbove(epicenter.z) && z_transfer & UP)
- explosion(GetAbove(epicenter), adj_range, adj_max_power, 0, UP, shaped)
- if(HasBelow(epicenter.z) && z_transfer & DOWN)
- explosion(GetBelow(epicenter), adj_range, adj_max_power, 0, DOWN, shaped)
-
- var/max_range = max(devastation_range, heavy_impact_range, light_impact_range)
-
- // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves.
- // Stereo users will also hear the direction of the explosion!
- // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions.
- // 3/7/14 will calculate to 80 + 35
- var/far_dist = 0
- far_dist += heavy_impact_range * 5
- far_dist += devastation_range * 20
- var/frequency = get_rand_frequency()
- for(var/mob/M in GLOB.player_list)
- if(M.z == epicenter.z)
- var/turf/M_turf = get_turf(M)
- var/dist = get_dist(M_turf, epicenter)
- // If inside the blast radius + world.view - 2
- if(dist <= round(max_range + world.view - 2, 1))
- M.playsound_local(epicenter, get_sfx("explosion"), 100, 1, frequency, falloff = 5) // get_sfx() is so that everyone gets the same sound
- else if(dist <= far_dist)
- var/far_volume = clamp(far_dist, 30, 50) // Volume is based on explosion size and dist
- far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion
- M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', far_volume, 1, frequency, falloff = 5)
-
- if(adminlog)
- log_and_message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]) (JMP)")
- var/approximate_intensity = (devastation_range * 3) + (heavy_impact_range * 2) + light_impact_range
- // Large enough explosion. For performance reasons, powernets will be rebuilt manually
- if(!GLOB.defer_powernet_rebuild && (approximate_intensity > 25))
- GLOB.defer_powernet_rebuild = 1
-
- if(heavy_impact_range > 1)
- var/datum/effect/explosion/E = new
- E.set_up(epicenter)
- E.start()
-
- var/power = devastation_range * 2 + heavy_impact_range + light_impact_range //The ranges add up, ie light 14 includes both heavy 7 and devestation 3. So this calculation means devestation counts for 4, heavy for 2 and light for 1 power, giving us a cap of 27 power.
- explosion_rec(epicenter, power, shaped)
-
- sleep(8)
-
- return 1
-
-
-
-/proc/secondaryexplosion(turf/epicenter, range)
- for(var/turf/tile in range(range, epicenter))
- tile.ex_act(EX_ACT_HEAVY)
-
-
-#undef EXPLOSION_RATIO_DEVASTATION
-#undef EXPLOSION_RATIO_HEAVY
-#undef EXPLOSION_RATIO_LIGHT
+ var/datum/explosiondata/data = new
+ data.epicenter = epicenter
+ data.devastation_range = devastation_range
+ data.heavy_impact_range = heavy_impact_range
+ data.light_impact_range = light_impact_range
+ data.flash_range = flash_range
+ data.adminlog = adminlog
+ data.z_transfer = z_transfer
+ data.spreading = spreading
+ data.rec_pow = max(0,devastation_range) * 2 + max(0,heavy_impact_range) + max(0,light_impact_range)
+
+ // queue work
+ SSexplosives.queue(data)
+
+ //Machines which report explosions.
+ for(var/thing in doppler_arrays)
+ var/obj/machinery/doppler_array/Array = thing
+ Array.sense_explosion(epicenter.x,epicenter.y,epicenter.z,devastation_range,heavy_impact_range,light_impact_range)
+
+// == Recursive Explosions stuff ==
+
+/client/proc/kaboom()
+ var/power = input(src, "power?", "power?") as num
+ var/turf/T = get_turf(src.mob)
+ var/datum/explosiondata/d = new
+ d.spreading = TRUE
+ d.epicenter = T
+ d.rec_pow = power
+ SSexplosives.queue(d)
+
+/atom
+ var/explosion_resistance
+
+/turf/space
+ explosion_resistance = 3
+
+/turf/simulated/open
+ explosion_resistance = 3
+
+/turf/simulated/floor
+ explosion_resistance = 1
+
+/turf/simulated/mineral
+ explosion_resistance = 2
+
+/turf/simulated/wall
+ explosion_resistance = 10
diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm
index 9d3d52735817c..a7cb2eda7517d 100644
--- a/code/game/objects/explosion_recursive.dm
+++ b/code/game/objects/explosion_recursive.dm
@@ -1,150 +1,414 @@
+#define EXPLFX_BOTH 3
+#define EXPLFX_SOUND 2
+#define EXPLFX_SHAKE 1
+#define EXPLFX_NONE 0
+
+SUBSYSTEM_DEF(explosives)
+ name = "Explosives"
+ wait = 1
+ flags = SS_NO_INIT | SS_BACKGROUND | SS_POST_FIRE_TIMING
+ priority = SS_PRIORITY_EXPLOSIVES
+ runlevels = RUNLEVELS_GAME
+
+ can_fire = FALSE // Start disabled, explosions will wake us if need be.
+
+ var/list/work_queue = list()
+ var/ticks_without_work = 0
+ var/list/explosion_turfs
+ var/explosion_in_progress
+
+ var/mc_notified = FALSE
+
+/datum/controller/subsystem/explosives/Recover()
+ work_queue = SSexplosives.work_queue
+ explosion_in_progress = SSexplosives.explosion_in_progress
+ explosion_turfs = SSexplosives.explosion_turfs
+
+/datum/controller/subsystem/explosives/fire(resumed = FALSE)
+ if(!length(work_queue))
+ ticks_without_work++
+ if (ticks_without_work > 5)
+ // All explosions handled, we can sleep now.
+ can_fire = FALSE
+
+ mc_notified = FALSE
+ Master.ExplosionEnd()
+ return
+ ticks_without_work = 0
+ if (!mc_notified)
+ Master.ExplosionStart()
+ mc_notified = TRUE
+ for (var/A in work_queue)
+ var/datum/explosiondata/data = A
-var/global/list/explosion_turfs = list()
+ if (data.spreading)
+ explosion_iter(data.epicenter, data.rec_pow, data.z_transfer)
+ else
+ explosion(data)
-var/global/explosion_in_progress = 0
+ work_queue -= data
+// Handle a non-recusrive explosion.
+/datum/controller/subsystem/explosives/proc/explosion(datum/explosiondata/data)
+ var/turf/epicenter = data.epicenter
+ var/devastation_range = data.devastation_range
+ var/heavy_impact_range = data.heavy_impact_range
+ var/light_impact_range = data.light_impact_range
+ var/flash_range = data.flash_range
+ var/adminlog = data.adminlog
+ var/z_transfer = data.z_transfer
+ var/power = data.rec_pow
-/proc/explosion_rec(turf/epicenter, power, shaped)
- var/loopbreak = 0
- while(explosion_in_progress)
- if(loopbreak >= 15) return
- sleep(10)
- loopbreak++
+ if(data.spreading)
+ explosion_iter(epicenter, power, z_transfer)
+ return
- if(power <= 0) return
epicenter = get_turf(epicenter)
-//[SIERRA-ADD] - MODPACK_RND
- if(!epicenter) return
- for(var/obj/item/device/beacon/explosion_watcher/W in explosion_watcher_list)
- if(get_dist(W, epicenter) < 10)
- W.react_explosion(epicenter, power)
-//[/SIERRA-ADD] - MODPACK_RND
-
- explosion_in_progress = 1
- explosion_turfs = list()
-
- explosion_turfs[epicenter] = power
-
- //This steap handles the gathering of turfs which will be ex_act() -ed in the next step. It also ensures each turf gets the maximum possible amount of power dealt to it.
- for(var/direction in GLOB.cardinal)
- var/turf/T = get_step(epicenter, direction)
- var/adj_power = power - epicenter.get_explosion_resistance()
- if(shaped)
- if (shaped == direction)
- adj_power *= 3
- else if (shaped == reverse_direction(direction))
- adj_power *= 0.10
- else
- adj_power *= 0.45
-
- if (adj_power > 0)
- T.explosion_spread(adj_power, direction)
-
- //This step applies the ex_act effects for the explosion, as planned in the previous step.
- for(var/spot in explosion_turfs)
- var/turf/T = spot
- if(explosion_turfs[T] <= 0) continue
- if(!T) continue
-
- //Wow severity looks confusing to calculate... Fret not, I didn't leave you with any additional instructions or help. (just kidding, see the line under the calculation)
- var/severity = explosion_turfs[T] // effective power on tile
- severity /= max(3, power / 3) // One third the total explosion power - One third because there are three power levels and I want each one to take up a third of the crater
- severity = clamp(severity, 1, 3) // Sanity
- severity = 4 - severity // Invert the value to accomodate lower numbers being a higher severity. Removing this inversion would require a lot of refactoring of math in `ex_act()` handlers.
- severity = floor(severity)
-
- var/x = T.x
- var/y = T.y
- var/z = T.z
- T.ex_act(severity)
- if(!T)
- T = locate(x,y,z)
-
- var/throw_target = get_edge_target_turf(T, get_dir(epicenter,T))
- for(var/atom_movable in T.contents)
- var/atom/movable/AM = atom_movable
- if(AM && AM.simulated && !T.protects_atom(AM))
- AM.ex_act(severity)
- if(!QDELETED(AM) && !AM.anchored)
- addtimer(new Callback(AM, TYPE_PROC_REF(/atom/movable, throw_at), throw_target, 9/severity, 9/severity), 0)
-
- explosion_turfs.Cut()
- explosion_in_progress = 0
-
-
-//Code-wise, a safe value for power is something up to ~25 or ~30.. This does quite a bit of damage to the station.
-//direction is the direction that the spread took to come to this tile. So it is pointing in the main blast direction - meaning where this tile should spread most of it's force.
-/turf/proc/explosion_spread(power, direction)
-
- if(explosion_turfs[src] >= power)
- return //The turf already sustained and spread a power greated than what we are dealing with. No point spreading again.
- explosion_turfs[src] = power
-/*
- sleep(2)
- var/obj/debugging/M = locate() in src
- if (!M)
- M = new(src, power, direction)
- M.maptext = "[power] vs [src.get_explosion_resistance()]"
- if(power > 10)
- M.color = "#cccc00"
- if(power > 20)
- M.color = "#ffcc00"
-*/
- var/spread_power = power - src.get_explosion_resistance() //This is the amount of power that will be spread to the tile in the direction of the blast
- if (spread_power <= 0)
+ if(!epicenter)
return
- var/turf/T = get_step(src, direction)
- if(T)
- T.explosion_spread(spread_power, direction)
- T = get_step(src, turn(direction,90))
- if(T)
- T.explosion_spread(spread_power, turn(direction,90))
- T = get_step(src, turn(direction,-90))
- if(T)
- T.explosion_spread(spread_power, turn(direction,90))
-
-/turf/unsimulated/explosion_spread(power)
- return //So it doesn't get to the parent proc, which simulates explosions
-
-/// Float. The atom's explosion resistance value. Used to calculate how much of an explosion is 'absorbed' and not passed on to tiles on the other side of the atom's turf. See `/proc/explosion_rec()`.
-/atom/var/explosion_resistance = 0
-
-/**
- * Retrieves the atom's explosion resistance. Generally, this is `explosion_resistance` for simulated atoms.
- */
-/atom/proc/get_explosion_resistance()
- if(simulated)
- return explosion_resistance
-
-/turf/get_explosion_resistance()
- . = ..()
- for(var/obj/O in src)
- . += O.get_explosion_resistance()
+ // Handles recursive propagation of explosions.
+ if(devastation_range > 2 || heavy_impact_range > 2)
+ if(HasAbove(epicenter.z) && z_transfer & UP)
+ global.explosion(GetAbove(epicenter), max(0, devastation_range - 2), max(0, heavy_impact_range - 2), max(0, light_impact_range - 2), max(0, flash_range - 2), 0, UP, spreading = FALSE)
+ if(HasBelow(epicenter.z) && z_transfer & DOWN)
+ global.explosion(GetBelow(epicenter), max(0, devastation_range - 2), max(0, heavy_impact_range - 2), max(0, light_impact_range - 2), max(0, flash_range - 2), 0, DOWN, spreading = FALSE)
+
+ var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flash_range)
+
+ // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves.
+ // Stereo users will also hear the direction of the explosion!
+ // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions.
+ // 3/7/14 will calculate to 80 + 35
+ var/far_dist = 0
+ far_dist += heavy_impact_range * 5
+ far_dist += devastation_range * 20
+ // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves.
+
+ // Stereo users will also hear the direction of the explosion!
+
+ // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions.
+
+ // 3/7/14 will calculate to 80 + 35
+ var/volume = 10 + (power * 20)
+
+ var/closedist = round(max_range + world.view - 2, 1)
+
+ //Whether or not this explosion causes enough vibration to send sound or shockwaves through the station
+ var/vibration = 1
+ if(istype(epicenter, /turf/space))
+ vibration = 0
+ for(var/thing in RANGED_TURFS(max_range, epicenter))
+ var/turf/T = thing
+ if (!istype(T, /turf/space))
+ //If there is a nonspace tile within the explosion radius
+ //Then we can reverberate shockwaves through that, and allow it to be felt in a vacuum
+ vibration = 1
+
+ if (vibration)
+ for(var/thing in GLOB.player_list)
+ var/mob/M = thing
+ CHECK_TICK
+ // Double check for client
+ var/reception = 2//Whether the person can be shaken or hear sound
+ //2 = BOTH
+ //1 = shockwaves only
+ //0 = no effect
+ if(M?.client)
+ var/turf/M_turf = get_turf(M)
+
+ if(M_turf && M_turf.z == epicenter.z)
+ if (istype(M_turf,/turf/space))
+ //If the person is standing in space, they wont hear
+ //But they may still feel the shaking
+ reception = 0
+ for(var/t_thing in RANGED_TURFS(1, M))
+ var/turf/T = t_thing
+ if(!istype(T, /turf/space))
+ //If theyre touching the hull or on some extruding part of the station
+ reception = 1//They will get screenshake
+ break
+
+ if (!reception)
+ continue
+
+ var/dist = get_dist(M_turf, epicenter)
+ var/explosion_dir = get_dir(M_turf, epicenter)
+ if (reception == 2 && (M.ear_deaf <= 0 || !M.ear_deaf))//Dont play sounds to deaf people
+ // If inside the blast radius + world.view - 2
+ if (dist <= closedist)
+ to_chat(M, FONT_LARGE(SPAN_WARNING("You hear the sound of a nearby explosion coming from \the [explosion_dir].")))
+ M.playsound_local(epicenter, get_sfx(GLOB.explosion_sound), min(100, volume), vary = TRUE, falloff = 5)
+ else if (dist > closedist) // People with sensitive hearing get a better idea of how far it is
+ to_chat(M, FONT_LARGE(SPAN_WARNING("You hear the sound of a semi-close explosion coming from \the [explosion_dir].")))
+ M.playsound_local(epicenter, get_sfx(GLOB.explosion_sound), min(100, volume), vary = TRUE, falloff = 5)
+ else //You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station.
+ volume = M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', volume, vary = TRUE, falloff = 1000)
+ if(volume)
+ to_chat(M, FONT_LARGE(SPAN_NOTICE("You hear the sound of a distant explosion coming from \the [explosion_dir].")))
+
+ //Deaf people will feel vibrations though
+ if (volume > 0)//Only shake camera if someone was close enough to hear it
+ shake_camera(M, min(30,max(2,(power*2) / dist)), min(3.5,((power/3) / dist)),0.05)
+ //Maximum duration is 3 seconds, and max strength is 3.5
+ //Becuse values higher than those just get really silly
+
+ if(adminlog)
+ message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]) (JMP)")
+ log_game("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range]) in area [epicenter.loc.name] ")
+
+ if(heavy_impact_range > 1)
+ var/datum/effect/explosion/E = new/datum/effect/explosion()
+ E.set_up(epicenter)
+ E.start()
+
+ var/x0 = epicenter.x
+ var/y0 = epicenter.y
+
+ for(var/thing in RANGED_TURFS(max_range, epicenter))
+ var/turf/T = thing
+ if (!T)
+ CHECK_TICK
+ continue
+
+ var/dist = sqrt((T.x - x0)**2 + (T.y - y0)**2)
+
+ if (dist < devastation_range)
+ dist = 1
+ else if (dist < heavy_impact_range)
+ dist = 2
+ else if (dist < light_impact_range)
+ dist = 3
+ else
+ CHECK_TICK
+ continue
+
+ T.ex_act(dist)
+ CHECK_TICK
+ if(T)
+ for(var/atom_movable in T.contents) //bypass type checking since only atom/movable can be contained by turfs anyway
+ var/atom/movable/AM = atom_movable
+ if(!QDELETED(AM) && AM.simulated)
+ var/obj/O = AM
+ if(istype(O) && O.hides_under_flooring() && !T.is_plating())
+ continue
+ AM.ex_act(dist)
+
+ CHECK_TICK
+
+// All the vars used on the turf should be on unsimulated turfs too, we just don't care about those generally.
+#define SEARCH_DIR(dir) \
+ search_direction = dir;\
+ search_turf = get_step(current_turf, search_direction);\
+ if (istype(search_turf, /turf/simulated)) {\
+ turf_queue += search_turf;\
+ dir_queue += search_direction;\
+ power_queue += current_power;\
+ }
+
+// Handle an iterative explosion.
+/datum/controller/subsystem/explosives/proc/explosion_iter(turf/epicenter, power, z_transfer)
+ if(power <= 0)
+ return
-/turf/space/explosion_resistance = 3
+ epicenter = get_turf(epicenter)
+ if(!epicenter)
+ return
-/turf/simulated/floor/get_explosion_resistance()
- . = ..()
- if(is_below_sound_pressure(src))
- . *= 3
+ message_admins("Explosion with size ([power]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]) (JMP)")
+ log_game("Explosion with size ([power]) in area [epicenter.loc.name] ")
+
+ log_debug("iexpl: Beginning discovery phase.")
+ var/time = world.time
+
+ explosion_in_progress = TRUE
+ var/list/act_turfs = list()
+ act_turfs[epicenter] = power
+
+ power -= epicenter.explosion_resistance
+ for (var/obj/O in epicenter)
+ if (O.explosion_resistance)
+ power -= O.explosion_resistance
+
+ if (power >= config.iterative_explosives_z_threshold)
+ if ((z_transfer & UP) && HasAbove(epicenter.z))
+ var/datum/explosiondata/data = new
+ data.epicenter = GetAbove(epicenter)
+ data.rec_pow = (power * config.iterative_explosives_z_multiplier) - config.iterative_explosives_z_subtraction
+ data.z_transfer = UP
+ data.spreading = TRUE
+ queue(data)
+
+ if ((z_transfer & DOWN) && HasBelow(epicenter.z))
+ var/datum/explosiondata/data = new
+ data.epicenter = GetBelow(epicenter)
+ data.rec_pow = (power * config.iterative_explosives_z_multiplier) - config.iterative_explosives_z_subtraction
+ data.z_transfer = DOWN
+ data.spreading = TRUE
+ queue(data)
+
+ // These three lists must always be the same length.
+ var/list/turf_queue = list(epicenter, epicenter, epicenter, epicenter)
+ var/list/dir_queue = list(NORTH, SOUTH, EAST, WEST)
+ var/list/power_queue = list(power, power, power, power)
+
+ var/turf/simulated/current_turf
+ var/turf/search_turf
+ var/origin_direction
+ var/search_direction
+ var/current_power
+ var/index = 1
+ while (index <= turf_queue.len)
+ current_turf = turf_queue[index]
+ origin_direction = dir_queue[index]
+ current_power = power_queue[index]
+ ++index
+
+ if (!istype(current_turf) || current_power <= 0)
+ CHECK_TICK
+ continue
+
+ if (act_turfs[current_turf] >= current_power && current_turf != epicenter)
+ CHECK_TICK
+ continue
+
+ act_turfs[current_turf] = current_power
+ current_power -= current_turf.explosion_resistance
+
+ // Attempt to shortcut on empty tiles: if a turf only has a LO on it, we don't need to check object resistance. Some turfs might not have LOs, so we need to check it actually has one.
+ if (current_turf.contents.len > !!current_turf.lighting_overlay)
+ for (var/thing in current_turf)
+ var/atom/movable/AM = thing
+ if (AM.simulated && AM.explosion_resistance)
+ current_power -= AM.explosion_resistance
+
+ if (current_power <= 0)
+ CHECK_TICK
+ continue
+
+ SEARCH_DIR(origin_direction)
+ SEARCH_DIR(turn(origin_direction, 90))
+ SEARCH_DIR(turn(origin_direction, -90))
+
+ CHECK_TICK
+
+ log_debug("iexpl: Discovery completed in [(world.time-time)/10] seconds.")
+ log_debug("iexpl: Beginning SFX phase.")
+ time = world.time
+
+ var/volume = 10 + (power * 20)
+
+ var/close_dist = round(power + world.view - 2, 1)
+
+ var/sound/explosion_sound = sound(get_sfx(GLOB.explosion_sound))
+
+ for (var/thing in GLOB.player_list)
+ var/mob/M = thing
+ var/reception = EXPLFX_BOTH
+
+ var/turf/T = isturf(M.loc) ? M.loc : get_turf(M)
+
+ if (!T)
+ CHECK_TICK
+ continue
+
+ if (!AreConnectedZLevels(T.z, epicenter.z))
+ CHECK_TICK
+ continue
+
+ if (T.type == /turf/space) // Equality is faster than istype.
+ reception = EXPLFX_NONE
+
+ for (var/turf/simulated/THING in RANGED_TURFS(1, M))
+ reception |= EXPLFX_SHAKE
+ break
+
+ if (!reception)
+ CHECK_TICK
+ continue
+
+ var/dist = get_dist(M, epicenter) || 1
+ if ((reception & EXPLFX_SOUND) && !isdeaf(M))
+ if (dist <= close_dist)
+ M.playsound_local(epicenter, explosion_sound, min(100, volume), vary = TRUE)
+ //You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station.
+ else
+ volume = M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', volume, vary = TRUE)
-/turf/simulated/wall/get_explosion_resistance()
- return 5 // Standardized health results in explosion_resistance being used to reduce overall damage taken, instead of changing explosion severity. 5 was the original default, so 5 is always returned here.
+ if ((reception & EXPLFX_SHAKE) && volume > 0)
+ shake_camera(M, min(30, max(2,(power*2) / dist)), min(3.5, ((power/3) / dist)),0.05)
+ //Maximum duration is 3 seconds, and max strength is 3.5
+ //Becuse values higher than those just get really silly
-/turf/simulated/floor/explosion_resistance = 1
+ CHECK_TICK
-/turf/simulated/mineral/explosion_resistance = 2
+ log_debug("iexpl: SFX phase completed in [(world.time-time)/10] seconds.")
+ log_debug("iexpl: Beginning application phase.")
+ time = world.time
-/turf/simulated/shuttle/wall/explosion_resistance = 10
+ var/turf_tally = 0
+ var/movable_tally = 0
+ for (var/thing in act_turfs)
+ var/turf/T = thing
+ if (act_turfs[T] <= 0)
+ CHECK_TICK
+ continue
-/turf/simulated/wall/explosion_resistance = 10
+ //Wow severity looks confusing to calculate... Fret not, I didn't leave you with any additional instructions or help. (just kidding, see the line under the calculation)
+ var/severity = 4 - round(max(min( 3, ((act_turfs[T] - T.explosion_resistance) / (max(3,(power/3)))) ) ,1), 1)
+ //sanity effective power on tile divided by either 3 or one third the total explosion power
+ // One third because there are three power levels and I
+ // want each one to take up a third of the crater
+
+ if (T.simulated)
+ T.ex_act(severity)
+ if (T.contents.len > !!T.lighting_overlay)
+ for (var/subthing in T)
+ var/atom/movable/AM = subthing
+ if (AM.simulated)
+ var/obj/O = AM
+ if(istype(O) && O.hides_under_flooring() && !T.is_plating())
+ continue
+ AM.ex_act(severity)
+ movable_tally++
+ CHECK_TICK
+ else
+ CHECK_TICK
+
+ turf_tally++
+
+ explosion_in_progress = FALSE
+ log_debug("iexpl: Application completed in [(world.time-time)/10] seconds; processed [turf_tally] turfs and [movable_tally] movables.")
+
+#undef SEARCH_DIR
+
+// Add an explosion to the queue for processing.
+/datum/controller/subsystem/explosives/proc/queue(datum/explosiondata/data)
+ if (!data || !istype(data))
+ return
-/obj/machinery/door/get_explosion_resistance()
- if(!density)
- return 0
- else
- return ..()
+ work_queue += data
+
+ // Wake it up from sleeping if necessary.
+ if (!can_fire)
+ can_fire = TRUE
+
+// The data datum for explosions.
+/datum/explosiondata
+ var/turf/epicenter
+ var/devastation_range
+ var/heavy_impact_range
+ var/light_impact_range
+ var/flash_range
+ var/adminlog
+ var/z_transfer
+ var/spreading
+ var/rec_pow
+
+#undef EXPLFX_BOTH
+#undef EXPLFX_SOUND
+#undef EXPLFX_SHAKE
+#undef EXPLFX_NONE
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 1204f55cc8112..86d9f6277e729 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -682,15 +682,11 @@ Ccomp's first proc.
max_power = EX_ACT_HEAVY
if ("Light")
max_power = EX_ACT_LIGHT
- var/shaped = 0
- if(alert(src, "Shaped explosion?", "Shape", "Yes", "No") == "Yes")
- shaped = input("Shaped where to?", "Input") as anything in list("NORTH","SOUTH","EAST","WEST")
- shaped = text2dir(shaped)
if (range > 20)
if (alert(src, "Are you sure you want to do this? It may lag.", "Confirmation", "Yes", "No") == "No")
return
- explosion(O, range, max_power, shaped=shaped)
+ explosion(O, range, max_power)
log_admin("[key_name(usr)] created an explosion ([range], [max_power_input]) at ([O.x],[O.y],[O.z])")
message_admins("[key_name_admin(usr)] created an explosion ([range], [max_power_input]) at ([O.x],[O.y],[O.z])", 1)
diff --git a/code/modules/shield_generators/shield.dm b/code/modules/shield_generators/shield.dm
index 5d1d1d8915dad..c73104128e621 100644
--- a/code/modules/shield_generators/shield.dm
+++ b/code/modules/shield_generators/shield.dm
@@ -274,9 +274,6 @@
else
explosion_resistance = 0
-/obj/shield/get_explosion_resistance()
- return explosion_resistance
-
// Shield collision checks below
/atom/movable/proc/can_pass_shield(obj/machinery/power/shield_generator/gen)