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)