From 04004f8d07340d1dae13fa1e2cc54884e649a224 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Fri, 17 Jan 2025 05:53:06 +0500 Subject: [PATCH 01/11] experiment: explosions subsystem --- code/__DEFINES/subsystems.dm | 2 + code/__HELPERS/data_struct/priority_queue.dm | 52 +++ code/__HELPERS/data_struct/queue.dm | 72 ++++ code/controllers/subsystem/explosions.dm | 424 +++++++++++++++++++ code/game/atoms.dm | 4 + code/game/objects/explosion.dm | 246 ----------- code/game/objects/obj_defense.dm | 3 - paradise.dme | 3 + 8 files changed, 557 insertions(+), 249 deletions(-) create mode 100644 code/__HELPERS/data_struct/priority_queue.dm create mode 100644 code/__HELPERS/data_struct/queue.dm create mode 100644 code/controllers/subsystem/explosions.dm diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 2e06c7645f1..ea94110d005 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -102,6 +102,7 @@ #define INIT_ORDER_NIGHTSHIFT -24 #define INIT_ORDER_GAME_EVENTS -26 #define INIT_ORDER_PATH -50 +#define INIT_ORDER_EXPLOSIONS -69 #define INIT_ORDER_PERSISTENCE -95 #define INIT_ORDER_STATPANELS -98 #define INIT_ORDER_DEMO -99 // To avoid a bunch of changes related to initialization being written, do this last @@ -139,6 +140,7 @@ #define FIRE_PRIORITY_CHAT 400 #define FIRE_PRIORITY_RUNECHAT 410 // I hate how high the fire priority on this is -aa #define FIRE_PRIORITY_OVERLAYS 500 +#define FIRE_PRIORITY_EXPLOSIONS 666 #define FIRE_PRIORITY_TIMER 700 #define FIRE_PRIORITY_SPEECH_CONTROLLER 900 #define FIRE_PRIORITY_DELAYED_VERBS 950 diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm new file mode 100644 index 00000000000..fad1b433c6f --- /dev/null +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -0,0 +1,52 @@ +/priority_queue + var/list/heap = list() + +/priority_queue/proc/enqueue(value, priority) + heap += list(list(priority, value)) + bubble_up(heap.len) + +/priority_queue/proc/dequeue() + if (heap.len == 0) + return null + + var/list/top = heap[1] + heap[1] = heap[heap.len] + heap.Cut(heap.len, heap.len) + bubble_down(1) + + return top[2] + +/priority_queue/proc/peek() + if (heap.len == 0) + return null + return heap[1][2] + +/priority_queue/proc/is_empty() + return heap.len == 0 + +/priority_queue/proc/bubble_up(index) + while (index > 1) + var/parent = round(index / 2) + if (heap[index][1] < heap[parent][1]) + swap(index, parent) + index = parent + else + break + +/priority_queue/proc/bubble_down(index) + while (index * 2 <= heap.len) + var/child = index * 2 + + if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) + child++ + + if (heap[child][1] < heap[index][1]) + swap(index, child) + index = child + else + break + +/priority_queue/proc/swap(a, b) + var/list/temp = heap[a] + heap[a] = heap[b] + heap[b] = temp diff --git a/code/__HELPERS/data_struct/queue.dm b/code/__HELPERS/data_struct/queue.dm new file mode 100644 index 00000000000..569cb875e9a --- /dev/null +++ b/code/__HELPERS/data_struct/queue.dm @@ -0,0 +1,72 @@ +/* +* Double linked list node +*/ +/node + var/value + var/prev + var/next + +/* +* Defining a queue based on a double linked list +*/ +/queue + /// Link to the beginning of the list + var/node/head + /// Link to end of list + var/node/tail + /// Number of elements in queue + var/count = 0 + +/* +* Adding an element to the end of the queue +*/ +/queue/proc/enqueue(value) + var/node/new_node = new + new_node.value = value + + if (!tail) + head = new_node + tail = new_node + else + tail.next = new_node + new_node.prev = tail + tail = new_node + count++ +/* + * Retrieving an element from the head of the queue + */ +/queue/proc/dequeue() + if (!head) + return null + + var/value = head.value + var/node/old_head = head + + head = head.next + if (head) + head.prev = null + else + tail = null + old_head.value = null + qdel(old_head) + count-- + return value +/* +* Returns an element from the beginning of the queue without removing it +*/ +/queue/proc/peek() + if (!head) + return null + return head.value + +/* +* Checking if the queue is empty +*/ +/queue/proc/is_empty() + return count == 0 + +/* +* Returns the number of elements in the queue +*/ +/queue/proc/size() + return count diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm new file mode 100644 index 00000000000..4984399bc07 --- /dev/null +++ b/code/controllers/subsystem/explosions.dm @@ -0,0 +1,424 @@ +//TODO: Flash range does nothing currently + +#define CREAK_DELAY 5 SECONDS //Time taken for the creak to play after explosion, if applicable. +#define DEVASTATION_PROB 30 //The probability modifier for devistation, maths! +#define HEAVY_IMPACT_PROB 5 //ditto +#define FAR_UPPER 60 //Upper limit for the far_volume, distance, clamped. +#define FAR_LOWER 40 //lower limit for the far_volume, distance, clamped. +#define PROB_SOUND 75 //The probability modifier for a sound to be an echo, or a far sound. (0-100) +#define SHAKE_CLAMP 2.5 //The limit for how much the camera can shake for out of view booms. +#define FREQ_UPPER 40 //The upper limit for the randomly selected frequency. +#define FREQ_LOWER 25 //The lower of the above. + +GLOBAL_LIST_EMPTY(explosions) + +SUBSYSTEM_DEF(explosions) + name = "Explosions" + init_order = INIT_ORDER_EXPLOSIONS + priority = FIRE_PRIORITY_EXPLOSIONS + wait = 1 + flags = SS_TICKER + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/priority_queue/explosion_queue + + var/reactionary_explosions = FALSE + var/multiz_explosions = FALSE + + // Explosion sounds cache + var/sound/explosion_sound + var/sound/far_explosion_sound + var/sound/creaking_explosion_sound + var/sound/hull_creaking_sound + var/sound/explosion_echo_sound + + +/datum/controller/subsystem/explosions/Initialize() + if(CONFIG_GET(flag/multiz_explosions)) + multiz_explosions = TRUE + if(CONFIG_GET(flag/reactionary_explosions)) + reactionary_explosions = TRUE + explosion_sound = sound(get_sfx("explosion")) + far_explosion_sound = sound('sound/effects/explosionfar.ogg') + creaking_explosion_sound = sound(get_sfx("explosion_creaking")) + hull_creaking_sound = sound(get_sfx("hull_creaking")) + explosion_echo_sound = sound('sound/effects/explosion_distant.ogg') + explosion_queue = new() + return SS_INIT_SUCCESS + + +/datum/controller/subsystem/explosions/fire() + while(!explosion_queue.is_empty()) + var/datum/explosion_data/data = explosion_queue.peek() + while(!data.affected_turfs_queue.is_empty()) + var/turf/T = data.affected_turfs_queue.dequeue() + if(QDELETED(T)) + continue + var/dist = HYPOTENUSE(T.x, T.y, data.x0, data.y0) + if(multiz_explosions && T.z != data.z0) + if(T.z < data.z0) // we hit the turf that is below our epicenter. Check epicenter turf + dist += data.floor_block["[T.z + 1]"] + 1 //cheapest way to implement hypotenuse with z coordinates + else + dist += data.floor_block["[T.z]"] + 1 + + if(reactionary_explosions) + var/turf/Trajectory = T + while(!(Trajectory in data.epicenter_list)) + Trajectory = get_step_towards(Trajectory, data.epicenter) + dist += data.cached_exp_block[Trajectory] + + var/flame_dist = 0 + + if(dist < data.flame_range) + flame_dist = 1 + + if(dist < data.devastation_range) dist = 1 + else if(dist < data.heavy_impact_range) dist = 2 + else if(dist < data.light_impact_range) dist = 3 + else dist = 0 + + //------- TURF FIRES ------- + + if(flame_dist && prob(40) && !isspaceturf(T) && !T.density) + new /obj/effect/hotspot(T) //Mostly for ambience! + if(dist > 0) + if(issimulatedturf(T)) + var/turf/simulated/S = T + var/affecting_level + if(dist == 1) + affecting_level = 1 + else + affecting_level = S.is_shielded() ? 2 : (S.intact ? 2 : 1) + for(var/atom/AM as anything in S) //bypass type checking since only atom can be contained by turfs anyway + if(!QDELETED(AM) && AM.simulated) + if(AM.level >= affecting_level) + AM.ex_act(dist, data.epicenter) + else + for(var/atom/AM as anything in T) //see above + if(!QDELETED(AM) && AM.simulated) + AM.ex_act(dist, data.epicenter) + if(data.breach) + T.ex_act(dist, data.epicenter) + else + T.ex_act(3, data.epicenter) + if(MC_TICK_CHECK) + return + + var/took = stop_watch(data.watch) + //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare + log_world("## DEBUG: Explosion([data.x0],[data.y0],[data.z0])(d[data.devastation_range],h[data.heavy_impact_range],l[data.light_impact_range]): Took [took] seconds.") + data.log_explosions_machines(took) + qdel(explosion_queue.dequeue()) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/explosions/proc/start_explosion(datum/explosion_data/data, adminlog, cause, smoke, silent) + if(adminlog) + data.explosion_log(cause) + if(!silent) + data.play_sounds_and_shake() + data.create_effect(smoke) + data.enqueue_affected_turfs(multiz_explosions, reactionary_explosions) + explosion_queue.enqueue(data, data.affected_turfs_queue.count) + +/datum/controller/subsystem/explosions/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0, silent = FALSE, smoke = TRUE, cause = null, breach = TRUE) + if(!epicenter) + return FALSE + + var/datum/explosion_data/data = new(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap, flame_range, breach) + INVOKE_ASYNC(src, PROC_REF(start_explosion), data, adminlog, cause, smoke, silent) + + return TRUE + + +/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach) + SSexplosions.explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach) + +/* +* DON'T USE THIS!!! It is not processed by the system and has no radius restrictions. +*/ +/proc/secondaryexplosion(turf/epicenter, range) + for(var/turf/tile in prepare_explosion_turfs(range, epicenter)) + tile.ex_act(2, epicenter) + +/datum/explosion_data + var/orig_dev_range + var/orig_heavy_range + var/orig_light_range + var/orig_max_distance + + var/turf/epicenter + + var/max_range + var/x0 + var/y0 + var/z0 + var/far_dist = 0 + var/flame_range + var/flash_range + var/devastation_range + var/heavy_impact_range + var/light_impact_range + var/breach + var/queue/affected_turfs_queue = new() + var/list/cached_exp_block = list() + var/list/epicenter_list = list() + var/list/floor_block = list() + var/watch + +/datum/explosion_data/New(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap = FALSE, flame_range = 0, breach = TRUE) + . = ..() + src.epicenter = get_turf(epicenter) + src.flame_range = flame_range + src.flash_range = flash_range + src.devastation_range = devastation_range + src.heavy_impact_range = heavy_impact_range + src.light_impact_range = light_impact_range + src.breach = breach + src.max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) + + orig_dev_range = devastation_range + orig_heavy_range = heavy_impact_range + orig_light_range = light_impact_range + + orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range) + + x0 = epicenter.x + y0 = epicenter.y + z0 = epicenter.z + + far_dist = 0 + far_dist += heavy_impact_range * 15 + far_dist += devastation_range * 20 + + if(!ignorecap) + clamp_ranges() + epicenter_list += epicenter + watch = start_watch() + +/datum/explosion_data/Destroy() + qdel(affected_turfs_queue) + . = ..() + +/datum/explosion_data/proc/clamp_ranges() + devastation_range = clamp(devastation_range, 0, GLOB.max_ex_devastation_range) + heavy_impact_range = clamp(heavy_impact_range, 0, GLOB.max_ex_heavy_range) + light_impact_range = clamp(light_impact_range, 0, GLOB.max_ex_light_range) + flash_range = clamp(flash_range, 0, GLOB.max_ex_flash_range) + flame_range = clamp(flame_range, 0, GLOB.max_ex_flame_range) + +/datum/explosion_data/proc/create_effect(smoke) + if(heavy_impact_range > 1) + var/datum/effect_system/explosion/E + if(smoke) + E = new /datum/effect_system/explosion/smoke + else + E = new + E.set_up(epicenter) + E.start() + +/datum/explosion_data/proc/enqueue_affected_turfs(multiz_explosions, reactionary_explosions) + var/list/affected_turfs = prepare_explosion_turfs(max_range, epicenter) + if(multiz_explosions) + get_multiz_affect(affected_turfs) + + if(reactionary_explosions) + count_reactionary_explosions(affected_turfs) + + for(var/turf in affected_turfs) + affected_turfs_queue.enqueue(turf) + +/datum/explosion_data/proc/get_multiz_affect(list/affected_turfs) + var/turf/above = GET_TURF_ABOVE(epicenter) + var/turf/below = GET_TURF_BELOW(epicenter) + floor_block["[z0]"] = epicenter.explosion_vertical_block + + //We check for multi-z here. So in the code below(readtional explosives), we don't need to care about checking for above or below. + if(above) + affected_turfs += prepare_explosion_turfs(max_range, above) + epicenter_list += above + floor_block["[above.z]"] = above.explosion_vertical_block + + if(below) + affected_turfs += prepare_explosion_turfs(max_range, below) + epicenter_list += below + floor_block["[below.z]"] = below.explosion_vertical_block + +/datum/explosion_data/proc/count_reactionary_explosions(list/affected_turfs) + for(var/turf/T as anything in affected_turfs) // we cache the explosion block rating of every turf in the explosion area + cached_exp_block[T] = 0 + if(T.density && T.explosion_block) + cached_exp_block[T] += T.explosion_block + + for(var/atom/O as anything in T) + var/the_block = O.explosion_block + cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + +/datum/explosion_data/proc/explosion_log(cause) + var/cause_str + var/atom/cause_atom + var/cause_vv = "" + if(isatom(cause)) + cause_atom = cause + cause_str = cause_atom.name + cause_vv += ADMIN_VV(cause_atom,"VV") + else if(istext(cause)) + cause_str = cause + else if(isnull(cause)) + pass() + else + log_runtime("Bad type of cause for logging explosion.") + + message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str] [cause_vv])" : ""] [ADMIN_VERBOSEJMP(epicenter)] ") + add_game_logs("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str])" : ""] [AREACOORD(epicenter)] ") + +/datum/explosion_data/proc/log_explosions_machines(took) + //Machines which report explosions. + for(var/array in GLOB.doppler_arrays) + if(!array) + continue + if(istype(array, /obj/machinery/doppler_array)) + var/obj/machinery/doppler_array/doppler_array = array + doppler_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) + if(istype(array, /obj/item/clothing/head/helmet/space/hardsuit/rd)) + var/obj/item/clothing/head/helmet/space/hardsuit/rd/helm_array = array + helm_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_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 +*/ +/datum/explosion_data/proc/play_sounds_and_shake() + var/frequency = get_rand_frequency() + var/on_station = is_station_level(epicenter.z) + var/creaking_explosion = FALSE + + if(prob(devastation_range * DEVASTATION_PROB + heavy_impact_range * HEAVY_IMPACT_PROB) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might. + creaking_explosion = TRUE // prob over 100 always returns true + + for(var/MN in GLOB.player_list) + var/mob/M = MN + // Double check for client + var/turf/M_turf = get_turf(M) + if(M_turf && M_turf.z == z0) + var/dist = get_dist(M_turf, epicenter) + var/baseshakeamount + if(orig_max_distance - dist > 0) + baseshakeamount = sqrt((orig_max_distance - dist) * 0.1) + // If inside the blast radius + world.view - 2 + if(dist <= round(max_range + world.view - 2, 1)) + M.playsound_local(epicenter, null, 100, 1, frequency, S = SSexplosions.explosion_sound) + if(baseshakeamount > 0) + shake_camera(M, 25, clamp(baseshakeamount, 0, 10)) + // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station. + else if(dist <= far_dist) + var/far_volume = clamp(far_dist / 2, FAR_LOWER, FAR_UPPER) // Volume is based on explosion size and dist + if(creaking_explosion) + M.playsound_local(epicenter, null, far_volume, 1, frequency, S = SSexplosions.creaking_explosion_sound, distance_multiplier = 0) + else if(prob(PROB_SOUND)) // Sound variety during meteor storm/tesloose/other bad event + M.playsound_local(epicenter, null, far_volume, 1, frequency, S = SSexplosions.far_explosion_sound, distance_multiplier = 0) // Far sound + else + M.playsound_local(epicenter, null, far_volume, 1, frequency, S = SSexplosions.explosion_echo_sound, distance_multiplier = 0) // Echo sound + + if(baseshakeamount > 0 || devastation_range) + if(!baseshakeamount) // Devastating explosions rock the station and ground + baseshakeamount = devastation_range * 3 + shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) + else if(!isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull + var/echo_volume = 40 + if(devastation_range) + baseshakeamount = devastation_range + shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) + echo_volume = 60 + M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = SSexplosions.explosion_echo_sound, distance_multiplier = 0) + + if(creaking_explosion) // 5 seconds after the bang, the station begins to creak + addtimer(CALLBACK(M, TYPE_PROC_REF(/mob, playsound_local), epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), 1, frequency, null, null, FALSE, SSexplosions.hull_creaking_sound, 0), CREAK_DELAY) + +/// Returns a list of turfs in X range from the epicenter +/// Returns in a unique order, spiraling outwards +/// This is done to ensure our progressive cache of blast resistance is always valid +/// This is quite fast +/proc/prepare_explosion_turfs(range, turf/epicenter, protect_epicenter, explosion_direction = 0, explosion_arc = 360) + var/list/outlist = list() + var/list/candidates = list() + + var/our_x = epicenter.x + var/our_y = epicenter.y + var/our_z = epicenter.z + + var/max_x = world.maxx + var/max_y = world.maxy + + // Work out the angles to explode between + var/first_angle_limit = WRAP(explosion_direction - explosion_arc * 0.5, 0, 360) + var/second_angle_limit = WRAP(explosion_direction + explosion_arc * 0.5, 0, 360) + + // Get everything in the right order + var/lower_angle_limit + var/upper_angle_limit + var/do_directional + var/reverse_angle + + // Work out which case we're in + if(first_angle_limit == second_angle_limit) // CASE A: FULL CIRCLE + do_directional = FALSE + else if(first_angle_limit < second_angle_limit) // CASE B: When the arc does not cross 0 degrees + lower_angle_limit = first_angle_limit + upper_angle_limit = second_angle_limit + do_directional = TRUE + reverse_angle = FALSE + else if (first_angle_limit > second_angle_limit) // CASE C: When the arc crosses 0 degrees + lower_angle_limit = second_angle_limit + upper_angle_limit = first_angle_limit + do_directional = TRUE + reverse_angle = TRUE + + if(!protect_epicenter) + if(!do_directional) + candidates += epicenter + else + outlist += epicenter + + for(var/i in 1 to range) + var/lowest_x = our_x - i + var/lowest_y = our_y - i + var/highest_x = our_x + i + var/highest_y = our_y + i + // top left to one before top right + if(highest_y <= max_y) + candidates += block(max(lowest_x, 1), highest_y, our_z, + min(highest_x - 1, max_x), highest_y, our_z) + // top right to one before bottom right + if(highest_x <= max_x) + candidates += block(highest_x, min(highest_y, max_y), our_z, + highest_x, max(lowest_y + 1, 1), our_z) + // bottom right to one before bottom left + if(lowest_y >= 1) + candidates += block(min(highest_x, max_x), lowest_y, our_z, + max(lowest_x + 1, 1), lowest_y, our_z) + // bottom left to one before top left + if(lowest_x >= 1) + candidates += block(lowest_x, max(lowest_y, 1), our_z, + lowest_x, min(highest_y - 1, max_y), our_z) + + if(!do_directional) + outlist = candidates + else + for(var/turf/candidate as anything in candidates) + var/angle = get_angle(epicenter, candidate) + if(ISINRANGE(angle, lower_angle_limit, upper_angle_limit) ^ reverse_angle) + outlist += candidate + return outlist + +#undef CREAK_DELAY +#undef DEVASTATION_PROB +#undef HEAVY_IMPACT_PROB +#undef FAR_UPPER +#undef FAR_LOWER +#undef PROB_SOUND +#undef SHAKE_CLAMP +#undef FREQ_UPPER +#undef FREQ_LOWER diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 71eff1cfe86..be349824328 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1748,3 +1748,7 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) */ /atom/proc/relaydrive(mob/living/user, direction) return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE) + +///returns how much the object blocks an explosion. Used by subtypes. +/atom/proc/GetExplosionBlock() + CRASH("Unimplemented GetExplosionBlock()") diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 8f8d4991d87..453551832ce 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -10,252 +10,6 @@ #define FREQ_UPPER 40 //The upper limit for the randomly selected frequency. #define FREQ_LOWER 25 //The lower of the above. -/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, ignorecap = 0, flame_range = 0, silent = 0, smoke = 1, var/cause = null, breach = TRUE) - epicenter = get_turf(epicenter) - if(!epicenter) - return - - // Archive the uncapped explosion for the doppler array - var/orig_dev_range = devastation_range - var/orig_heavy_range = heavy_impact_range - var/orig_light_range = light_impact_range - - var/orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range) - - if(!ignorecap) - // Clamp all minimal values to 0, due to negative explosions doesn't exist - devastation_range = max(devastation_range, 0) - heavy_impact_range = max(heavy_impact_range, 0) - light_impact_range = max(light_impact_range, 0) - flash_range = max(flash_range, 0) - flame_range = max(flame_range, 0) - - // Clamp all values to MAX_EXPLOSION_RANGE - devastation_range = min (GLOB.max_ex_devastation_range, devastation_range) - heavy_impact_range = min (GLOB.max_ex_heavy_range, heavy_impact_range) - light_impact_range = min (GLOB.max_ex_light_range, light_impact_range) - flash_range = min (GLOB.max_ex_flash_range, flash_range) - flame_range = min (GLOB.max_ex_flame_range, flame_range) - - var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) - - spawn(0) - var/watch = start_watch() - - var/list/cached_exp_block = list() - var/multiz_explosions = FALSE - if(CONFIG_GET(flag/multiz_explosions)) - multiz_explosions = TRUE - var/reactionary_explosions = FALSE - if(CONFIG_GET(flag/reactionary_explosions)) - reactionary_explosions = TRUE - - if(adminlog) - var/cause_str - var/atom/cause_atom - var/cause_vv = "" - if(isatom(cause)) - cause_atom = cause - cause_str = cause_atom.name - cause_vv += ADMIN_VV(cause_atom,"VV") - else if(istext(cause)) - cause_str = cause - else if(isnull(cause)) - pass() - else - log_runtime("Bad type of cause for logging explosion.") - - message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str] [cause_vv])" : ""] [ADMIN_VERBOSEJMP(epicenter)] ") - add_game_logs("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str])" : ""] [AREACOORD(epicenter)] ") - - var/x0 = epicenter.x - var/y0 = epicenter.y - var/z0 = epicenter.z - - // 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 * 15 - far_dist += devastation_range * 20 - - if(!silent) - var/frequency = get_rand_frequency() - var/sound/explosion_sound = sound(get_sfx("explosion")) - var/sound/far_explosion_sound = sound('sound/effects/explosionfar.ogg') - var/sound/creaking_explosion_sound = sound(get_sfx("explosion_creaking")) - var/sound/hull_creaking_sound = sound(get_sfx("hull_creaking")) - var/sound/explosion_echo_sound = sound('sound/effects/explosion_distant.ogg') - var/on_station = is_station_level(epicenter.z) - var/creaking_explosion = FALSE - - if(prob(devastation_range * DEVASTATION_PROB + heavy_impact_range * HEAVY_IMPACT_PROB) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might. - creaking_explosion = TRUE // prob over 100 always returns true - - for(var/MN in GLOB.player_list) - var/mob/M = MN - // Double check for client - var/turf/M_turf = get_turf(M) - if(M_turf && M_turf.z == z0) - var/dist = get_dist(M_turf, epicenter) - var/baseshakeamount - if(orig_max_distance - dist > 0) - baseshakeamount = sqrt((orig_max_distance - dist) * 0.1) - // If inside the blast radius + world.view - 2 - if(dist <= round(max_range + world.view - 2, 1)) - M.playsound_local(epicenter, null, 100, 1, frequency, S = explosion_sound) - if(baseshakeamount > 0) - shake_camera(M, 25, clamp(baseshakeamount, 0, 10)) - // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station. - else if(dist <= far_dist) - var/far_volume = clamp(far_dist / 2, FAR_LOWER, FAR_UPPER) // Volume is based on explosion size and dist - if(creaking_explosion) - M.playsound_local(epicenter, null, far_volume, 1, frequency, S = creaking_explosion_sound, distance_multiplier = 0) - else if(prob(PROB_SOUND)) // Sound variety during meteor storm/tesloose/other bad event - M.playsound_local(epicenter, null, far_volume, 1, frequency, S = far_explosion_sound, distance_multiplier = 0) // Far sound - else - M.playsound_local(epicenter, null, far_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0) // Echo sound - - if(baseshakeamount > 0 || devastation_range) - if(!baseshakeamount) // Devastating explosions rock the station and ground - baseshakeamount = devastation_range * 3 - shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) - else if(!isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull - var/echo_volume = 40 - if(devastation_range) - baseshakeamount = devastation_range - shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) - echo_volume = 60 - M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0) - - if(creaking_explosion) // 5 seconds after the bang, the station begins to creak - addtimer(CALLBACK(M, TYPE_PROC_REF(/mob, playsound_local), epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), 1, frequency, null, null, FALSE, hull_creaking_sound, 0), CREAK_DELAY) - - if(heavy_impact_range > 1) - var/datum/effect_system/explosion/E - if(smoke) - E = new /datum/effect_system/explosion/smoke - else - E = new - E.set_up(epicenter) - E.start() - - var/list/affected_turfs = spiral_range_turfs(max_range, epicenter) - var/list/epicenter_list = list(epicenter) - var/list/floor_block = list() // [z] = num_block - if(multiz_explosions) - var/turf/above = GET_TURF_ABOVE(epicenter) - var/turf/below = GET_TURF_BELOW(epicenter) - floor_block["[z0]"] = epicenter.explosion_vertical_block - - //We check for multi-z here. So in the code below(readtional explosives), we don't need to care about checking for above or below. - if(above) - affected_turfs += spiral_range_turfs(max_range, above) - epicenter_list += above - floor_block["[above.z]"] = above.explosion_vertical_block - - if(below) - affected_turfs += spiral_range_turfs(max_range, below) - epicenter_list += below - floor_block["[below.z]"] = below.explosion_vertical_block - - if(reactionary_explosions) - for(var/A in affected_turfs) // we cache the explosion block rating of every turf in the explosion area - var/turf/T = A - cached_exp_block[T] = 0 - if(T.density && T.explosion_block) - cached_exp_block[T] += T.explosion_block - - for(var/obj/O in T) - var/the_block = O.explosion_block - cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block - CHECK_TICK - - for(var/A in affected_turfs) - var/turf/T = A - if(!T) - continue - var/dist = HYPOTENUSE(T.x, T.y, x0, y0) - if(multiz_explosions && T.z != z0) - if(T.z < z0) // we hit the turf that is below our epicenter. Check epicenter turf - dist += floor_block["[T.z + 1]"] + 1 //cheapest way to implement hypotenuse with z coordinates - else - dist += floor_block["[T.z]"] + 1 - - - if(reactionary_explosions) - var/turf/Trajectory = T - while(!(Trajectory in epicenter_list)) - Trajectory = get_step_towards(Trajectory, epicenter) - dist += cached_exp_block[Trajectory] - - var/flame_dist = 0 -// var/throw_dist = max_range - dist - - if(dist < flame_range) - flame_dist = 1 - - if(dist < devastation_range) dist = 1 - else if(dist < heavy_impact_range) dist = 2 - else if(dist < light_impact_range) dist = 3 - else dist = 0 - - //------- TURF FIRES ------- - - if(T) - if(flame_dist && prob(40) && !isspaceturf(T) && !T.density) - new /obj/effect/hotspot(T) //Mostly for ambience! - if(dist > 0) - if(issimulatedturf(T)) - var/turf/simulated/S = T - var/affecting_level - if(dist == 1) - affecting_level = 1 - else - affecting_level = S.is_shielded() ? 2 : (S.intact ? 2 : 1) - for(var/atom in S.contents) //bypass type checking since only atom can be contained by turfs anyway - var/atom/AM = atom - if(!QDELETED(AM) && AM.simulated) - if(AM.level >= affecting_level) - AM.ex_act(dist, epicenter) - else - for(var/atom in T.contents) //see above - var/atom/AM = atom - if(!QDELETED(AM) && AM.simulated) - AM.ex_act(dist, epicenter) - CHECK_TICK - if(breach) - T.ex_act(dist, epicenter) - else - T.ex_act(3, epicenter) - - CHECK_TICK - - var/took = stop_watch(watch) - //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare - log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.") - - //Machines which report explosions. - for(var/array in GLOB.doppler_arrays) - if(!array) - continue - if(istype(array, /obj/machinery/doppler_array)) - var/obj/machinery/doppler_array/doppler_array = array - doppler_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) - if(istype(array, /obj/item/clothing/head/helmet/space/hardsuit/rd)) - var/obj/item/clothing/head/helmet/space/hardsuit/rd/helm_array = array - helm_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) - return 1 - - - -/proc/secondaryexplosion(turf/epicenter, range) - for(var/turf/tile in spiral_range_turfs(range, epicenter)) - tile.ex_act(2, epicenter) - /client/proc/check_bomb_impacts() set name = "Check Bomb Impact" set category = "Debug" diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 094dc3a3d10..75905e6c15a 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -310,6 +310,3 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e var/mob/living/buckled_mob = m buckled_mob.electrocute_act((clamp(round(strength * 1.25e-3), 10, 90) + rand(-5, 5)), src, flags = SHOCK_TESLA) -///returns how much the object blocks an explosion. Used by subtypes. -/obj/proc/GetExplosionBlock() - CRASH("Unimplemented GetExplosionBlock()") diff --git a/paradise.dme b/paradise.dme index c464a36695a..367ec0872de 100644 --- a/paradise.dme +++ b/paradise.dme @@ -219,6 +219,8 @@ #include "code\__HELPERS\unsorted.dm" #include "code\__HELPERS\verb_helpers.dm" #include "code\__HELPERS\visual_effects.dm" +#include "code\__HELPERS\data_struct\priority_queue.dm" +#include "code\__HELPERS\data_struct\queue.dm" #include "code\__HELPERS\paths\jps.dm" #include "code\__HELPERS\paths\path.dm" #include "code\__HELPERS\paths\sssp.dm" @@ -329,6 +331,7 @@ #include "code\controllers\subsystem\demo.dm" #include "code\controllers\subsystem\early_assets.dm" #include "code\controllers\subsystem\events.dm" +#include "code\controllers\subsystem\explosions.dm" #include "code\controllers\subsystem\fires.dm" #include "code\controllers\subsystem\fluids.dm" #include "code\controllers\subsystem\game_events.dm" From ec1e08d50d5195c9f77f3c0cbfad324c561f4126 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:53:06 +0500 Subject: [PATCH 02/11] style fix --- code/__HELPERS/data_struct/priority_queue.dm | 19 ++++++++++--------- code/controllers/subsystem/explosions.dm | 2 +- code/game/atoms.dm | 4 ++-- code/game/machinery/doors/door.dm | 2 +- code/game/objects/explosion.dm | 2 +- code/game/objects/structures/window.dm | 2 +- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm index fad1b433c6f..b41bc092edf 100644 --- a/code/__HELPERS/data_struct/priority_queue.dm +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -34,17 +34,18 @@ break /priority_queue/proc/bubble_down(index) - while (index * 2 <= heap.len) - var/child = index * 2 + while(index * 2 <= heap.len) + var/child = index * 2 - if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) - child++ + if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) + child++ + + if (heap[child][1] >= heap[index][1]) + break + + swap(index, child) + index = child - if (heap[child][1] < heap[index][1]) - swap(index, child) - index = child - else - break /priority_queue/proc/swap(a, b) var/list/temp = heap[a] diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index 4984399bc07..ef01a937e4b 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -252,7 +252,7 @@ SUBSYSTEM_DEF(explosions) for(var/atom/O as anything in T) var/the_block = O.explosion_block - cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.get_explosion_block() : the_block /datum/explosion_data/proc/explosion_log(cause) var/cause_str diff --git a/code/game/atoms.dm b/code/game/atoms.dm index be349824328..76006303da8 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1750,5 +1750,5 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE) ///returns how much the object blocks an explosion. Used by subtypes. -/atom/proc/GetExplosionBlock() - CRASH("Unimplemented GetExplosionBlock()") +/atom/proc/get_explosion_block() + CRASH("Unimplemented get_explosion_block()") diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index a399d818477..058399d9bf2 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -528,5 +528,5 @@ ..(severity ? max(1, severity - 1) : 0) -/obj/machinery/door/GetExplosionBlock() +/obj/machinery/door/get_explosion_block() return density ? real_explosion_block : 0 diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 453551832ce..5e60931d995 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -84,7 +84,7 @@ for(var/obj/O in T) var/the_block = O.explosion_block - dist += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + dist += the_block == EXPLOSION_BLOCK_PROC ? O.get_explosion_block() : the_block if(dist < dev) T.color = "red" diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index ef7dfd250ba..4c15926b394 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -516,7 +516,7 @@ GLOBAL_LIST_INIT(wcCommon, pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e", C.throw_at(throwingdatum.initial_target, throwingdatum.maxrange - 1, throwingdatum.speed - 1) //Annnnnnnd yeet them into space, but slower, now that everything is dealt with -/obj/structure/window/GetExplosionBlock() +/obj/structure/window/get_explosion_block() return reinf && fulltile ? real_explosion_block : 0 /obj/structure/window/basic From bc54b78181e72b89f24681f36678aff14ad4f859 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:00:01 +0500 Subject: [PATCH 03/11] fix --- code/__HELPERS/data_struct/priority_queue.dm | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm index b41bc092edf..b27b7796d9f 100644 --- a/code/__HELPERS/data_struct/priority_queue.dm +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -25,13 +25,15 @@ return heap.len == 0 /priority_queue/proc/bubble_up(index) - while (index > 1) - var/parent = round(index / 2) - if (heap[index][1] < heap[parent][1]) - swap(index, parent) - index = parent - else - break + while(index > 1) + var/parent = round(index / 2) + + if (heap[parent][1] < heap[index][1]) + break + + swap(index, parent) + index = parent + /priority_queue/proc/bubble_down(index) while(index * 2 <= heap.len) @@ -40,7 +42,7 @@ if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) child++ - if (heap[child][1] >= heap[index][1]) + if (heap[index][1] < heap[child][1]) break swap(index, child) From b745ad6c26668397b971cb83ce73c5b6d93e80c4 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Wed, 22 Jan 2025 05:12:24 +0500 Subject: [PATCH 04/11] infinity cycle fix --- code/__HELPERS/data_struct/priority_queue.dm | 52 +++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm index b27b7796d9f..f2a0808fb04 100644 --- a/code/__HELPERS/data_struct/priority_queue.dm +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -1,34 +1,48 @@ /priority_queue - var/list/heap = list() + var/list/priority_node/heap = list() -/priority_queue/proc/enqueue(value, priority) - heap += list(list(priority, value)) - bubble_up(heap.len) +/priority_node + var/item + var/priority -/priority_queue/proc/dequeue() - if (heap.len == 0) - return null +/priority_node/New(item, priority) + . = ..() + src.item = item + src.priority = priority - var/list/top = heap[1] - heap[1] = heap[heap.len] - heap.Cut(heap.len, heap.len) - bubble_down(1) +/priority_queue/proc/enqueue(value, priority) + heap += list(new /priority_node(value, priority)) + bubble_up(heap.len) - return top[2] +/priority_queue/proc/dequeue() + if (heap.len == 0) + return null + + var/priority_node/top = heap[1] + var/bottom = heap[heap.len] + var/item = top.item + heap -= bottom + if(!heap.len) + qdel(top) + return item + heap[1] = bottom + bubble_down(1) + qdel(top) + return item /priority_queue/proc/peek() - if (heap.len == 0) - return null - return heap[1][2] + if (heap.len == 0) + return null + return heap[1].item /priority_queue/proc/is_empty() - return heap.len == 0 + return heap.len == 0 /priority_queue/proc/bubble_up(index) while(index > 1) var/parent = round(index / 2) - if (heap[parent][1] < heap[index][1]) + if (heap[parent].priority < heap[index].priority) break swap(index, parent) @@ -39,10 +53,10 @@ while(index * 2 <= heap.len) var/child = index * 2 - if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) + if (child + 1 <= heap.len && heap[child + 1].priority < heap[child].priority) child++ - if (heap[index][1] < heap[child][1]) + if (heap[index].priority < heap[child].priority) break swap(index, child) From a146d8c02eac5e29d28ec934c31e6df909a1c2b7 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Fri, 17 Jan 2025 05:53:06 +0500 Subject: [PATCH 05/11] experiment: explosions subsystem --- code/__DEFINES/subsystems.dm | 2 + code/__HELPERS/data_struct/priority_queue.dm | 52 +++ code/__HELPERS/data_struct/queue.dm | 72 ++++ code/controllers/subsystem/explosions.dm | 424 +++++++++++++++++++ code/game/atoms.dm | 4 + code/game/objects/explosion.dm | 246 ----------- code/game/objects/obj_defense.dm | 3 - paradise.dme | 3 + 8 files changed, 557 insertions(+), 249 deletions(-) create mode 100644 code/__HELPERS/data_struct/priority_queue.dm create mode 100644 code/__HELPERS/data_struct/queue.dm create mode 100644 code/controllers/subsystem/explosions.dm diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 2e06c7645f1..ea94110d005 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -102,6 +102,7 @@ #define INIT_ORDER_NIGHTSHIFT -24 #define INIT_ORDER_GAME_EVENTS -26 #define INIT_ORDER_PATH -50 +#define INIT_ORDER_EXPLOSIONS -69 #define INIT_ORDER_PERSISTENCE -95 #define INIT_ORDER_STATPANELS -98 #define INIT_ORDER_DEMO -99 // To avoid a bunch of changes related to initialization being written, do this last @@ -139,6 +140,7 @@ #define FIRE_PRIORITY_CHAT 400 #define FIRE_PRIORITY_RUNECHAT 410 // I hate how high the fire priority on this is -aa #define FIRE_PRIORITY_OVERLAYS 500 +#define FIRE_PRIORITY_EXPLOSIONS 666 #define FIRE_PRIORITY_TIMER 700 #define FIRE_PRIORITY_SPEECH_CONTROLLER 900 #define FIRE_PRIORITY_DELAYED_VERBS 950 diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm new file mode 100644 index 00000000000..fad1b433c6f --- /dev/null +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -0,0 +1,52 @@ +/priority_queue + var/list/heap = list() + +/priority_queue/proc/enqueue(value, priority) + heap += list(list(priority, value)) + bubble_up(heap.len) + +/priority_queue/proc/dequeue() + if (heap.len == 0) + return null + + var/list/top = heap[1] + heap[1] = heap[heap.len] + heap.Cut(heap.len, heap.len) + bubble_down(1) + + return top[2] + +/priority_queue/proc/peek() + if (heap.len == 0) + return null + return heap[1][2] + +/priority_queue/proc/is_empty() + return heap.len == 0 + +/priority_queue/proc/bubble_up(index) + while (index > 1) + var/parent = round(index / 2) + if (heap[index][1] < heap[parent][1]) + swap(index, parent) + index = parent + else + break + +/priority_queue/proc/bubble_down(index) + while (index * 2 <= heap.len) + var/child = index * 2 + + if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) + child++ + + if (heap[child][1] < heap[index][1]) + swap(index, child) + index = child + else + break + +/priority_queue/proc/swap(a, b) + var/list/temp = heap[a] + heap[a] = heap[b] + heap[b] = temp diff --git a/code/__HELPERS/data_struct/queue.dm b/code/__HELPERS/data_struct/queue.dm new file mode 100644 index 00000000000..569cb875e9a --- /dev/null +++ b/code/__HELPERS/data_struct/queue.dm @@ -0,0 +1,72 @@ +/* +* Double linked list node +*/ +/node + var/value + var/prev + var/next + +/* +* Defining a queue based on a double linked list +*/ +/queue + /// Link to the beginning of the list + var/node/head + /// Link to end of list + var/node/tail + /// Number of elements in queue + var/count = 0 + +/* +* Adding an element to the end of the queue +*/ +/queue/proc/enqueue(value) + var/node/new_node = new + new_node.value = value + + if (!tail) + head = new_node + tail = new_node + else + tail.next = new_node + new_node.prev = tail + tail = new_node + count++ +/* + * Retrieving an element from the head of the queue + */ +/queue/proc/dequeue() + if (!head) + return null + + var/value = head.value + var/node/old_head = head + + head = head.next + if (head) + head.prev = null + else + tail = null + old_head.value = null + qdel(old_head) + count-- + return value +/* +* Returns an element from the beginning of the queue without removing it +*/ +/queue/proc/peek() + if (!head) + return null + return head.value + +/* +* Checking if the queue is empty +*/ +/queue/proc/is_empty() + return count == 0 + +/* +* Returns the number of elements in the queue +*/ +/queue/proc/size() + return count diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm new file mode 100644 index 00000000000..4984399bc07 --- /dev/null +++ b/code/controllers/subsystem/explosions.dm @@ -0,0 +1,424 @@ +//TODO: Flash range does nothing currently + +#define CREAK_DELAY 5 SECONDS //Time taken for the creak to play after explosion, if applicable. +#define DEVASTATION_PROB 30 //The probability modifier for devistation, maths! +#define HEAVY_IMPACT_PROB 5 //ditto +#define FAR_UPPER 60 //Upper limit for the far_volume, distance, clamped. +#define FAR_LOWER 40 //lower limit for the far_volume, distance, clamped. +#define PROB_SOUND 75 //The probability modifier for a sound to be an echo, or a far sound. (0-100) +#define SHAKE_CLAMP 2.5 //The limit for how much the camera can shake for out of view booms. +#define FREQ_UPPER 40 //The upper limit for the randomly selected frequency. +#define FREQ_LOWER 25 //The lower of the above. + +GLOBAL_LIST_EMPTY(explosions) + +SUBSYSTEM_DEF(explosions) + name = "Explosions" + init_order = INIT_ORDER_EXPLOSIONS + priority = FIRE_PRIORITY_EXPLOSIONS + wait = 1 + flags = SS_TICKER + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/priority_queue/explosion_queue + + var/reactionary_explosions = FALSE + var/multiz_explosions = FALSE + + // Explosion sounds cache + var/sound/explosion_sound + var/sound/far_explosion_sound + var/sound/creaking_explosion_sound + var/sound/hull_creaking_sound + var/sound/explosion_echo_sound + + +/datum/controller/subsystem/explosions/Initialize() + if(CONFIG_GET(flag/multiz_explosions)) + multiz_explosions = TRUE + if(CONFIG_GET(flag/reactionary_explosions)) + reactionary_explosions = TRUE + explosion_sound = sound(get_sfx("explosion")) + far_explosion_sound = sound('sound/effects/explosionfar.ogg') + creaking_explosion_sound = sound(get_sfx("explosion_creaking")) + hull_creaking_sound = sound(get_sfx("hull_creaking")) + explosion_echo_sound = sound('sound/effects/explosion_distant.ogg') + explosion_queue = new() + return SS_INIT_SUCCESS + + +/datum/controller/subsystem/explosions/fire() + while(!explosion_queue.is_empty()) + var/datum/explosion_data/data = explosion_queue.peek() + while(!data.affected_turfs_queue.is_empty()) + var/turf/T = data.affected_turfs_queue.dequeue() + if(QDELETED(T)) + continue + var/dist = HYPOTENUSE(T.x, T.y, data.x0, data.y0) + if(multiz_explosions && T.z != data.z0) + if(T.z < data.z0) // we hit the turf that is below our epicenter. Check epicenter turf + dist += data.floor_block["[T.z + 1]"] + 1 //cheapest way to implement hypotenuse with z coordinates + else + dist += data.floor_block["[T.z]"] + 1 + + if(reactionary_explosions) + var/turf/Trajectory = T + while(!(Trajectory in data.epicenter_list)) + Trajectory = get_step_towards(Trajectory, data.epicenter) + dist += data.cached_exp_block[Trajectory] + + var/flame_dist = 0 + + if(dist < data.flame_range) + flame_dist = 1 + + if(dist < data.devastation_range) dist = 1 + else if(dist < data.heavy_impact_range) dist = 2 + else if(dist < data.light_impact_range) dist = 3 + else dist = 0 + + //------- TURF FIRES ------- + + if(flame_dist && prob(40) && !isspaceturf(T) && !T.density) + new /obj/effect/hotspot(T) //Mostly for ambience! + if(dist > 0) + if(issimulatedturf(T)) + var/turf/simulated/S = T + var/affecting_level + if(dist == 1) + affecting_level = 1 + else + affecting_level = S.is_shielded() ? 2 : (S.intact ? 2 : 1) + for(var/atom/AM as anything in S) //bypass type checking since only atom can be contained by turfs anyway + if(!QDELETED(AM) && AM.simulated) + if(AM.level >= affecting_level) + AM.ex_act(dist, data.epicenter) + else + for(var/atom/AM as anything in T) //see above + if(!QDELETED(AM) && AM.simulated) + AM.ex_act(dist, data.epicenter) + if(data.breach) + T.ex_act(dist, data.epicenter) + else + T.ex_act(3, data.epicenter) + if(MC_TICK_CHECK) + return + + var/took = stop_watch(data.watch) + //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare + log_world("## DEBUG: Explosion([data.x0],[data.y0],[data.z0])(d[data.devastation_range],h[data.heavy_impact_range],l[data.light_impact_range]): Took [took] seconds.") + data.log_explosions_machines(took) + qdel(explosion_queue.dequeue()) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/explosions/proc/start_explosion(datum/explosion_data/data, adminlog, cause, smoke, silent) + if(adminlog) + data.explosion_log(cause) + if(!silent) + data.play_sounds_and_shake() + data.create_effect(smoke) + data.enqueue_affected_turfs(multiz_explosions, reactionary_explosions) + explosion_queue.enqueue(data, data.affected_turfs_queue.count) + +/datum/controller/subsystem/explosions/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0, silent = FALSE, smoke = TRUE, cause = null, breach = TRUE) + if(!epicenter) + return FALSE + + var/datum/explosion_data/data = new(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap, flame_range, breach) + INVOKE_ASYNC(src, PROC_REF(start_explosion), data, adminlog, cause, smoke, silent) + + return TRUE + + +/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach) + SSexplosions.explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach) + +/* +* DON'T USE THIS!!! It is not processed by the system and has no radius restrictions. +*/ +/proc/secondaryexplosion(turf/epicenter, range) + for(var/turf/tile in prepare_explosion_turfs(range, epicenter)) + tile.ex_act(2, epicenter) + +/datum/explosion_data + var/orig_dev_range + var/orig_heavy_range + var/orig_light_range + var/orig_max_distance + + var/turf/epicenter + + var/max_range + var/x0 + var/y0 + var/z0 + var/far_dist = 0 + var/flame_range + var/flash_range + var/devastation_range + var/heavy_impact_range + var/light_impact_range + var/breach + var/queue/affected_turfs_queue = new() + var/list/cached_exp_block = list() + var/list/epicenter_list = list() + var/list/floor_block = list() + var/watch + +/datum/explosion_data/New(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap = FALSE, flame_range = 0, breach = TRUE) + . = ..() + src.epicenter = get_turf(epicenter) + src.flame_range = flame_range + src.flash_range = flash_range + src.devastation_range = devastation_range + src.heavy_impact_range = heavy_impact_range + src.light_impact_range = light_impact_range + src.breach = breach + src.max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) + + orig_dev_range = devastation_range + orig_heavy_range = heavy_impact_range + orig_light_range = light_impact_range + + orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range) + + x0 = epicenter.x + y0 = epicenter.y + z0 = epicenter.z + + far_dist = 0 + far_dist += heavy_impact_range * 15 + far_dist += devastation_range * 20 + + if(!ignorecap) + clamp_ranges() + epicenter_list += epicenter + watch = start_watch() + +/datum/explosion_data/Destroy() + qdel(affected_turfs_queue) + . = ..() + +/datum/explosion_data/proc/clamp_ranges() + devastation_range = clamp(devastation_range, 0, GLOB.max_ex_devastation_range) + heavy_impact_range = clamp(heavy_impact_range, 0, GLOB.max_ex_heavy_range) + light_impact_range = clamp(light_impact_range, 0, GLOB.max_ex_light_range) + flash_range = clamp(flash_range, 0, GLOB.max_ex_flash_range) + flame_range = clamp(flame_range, 0, GLOB.max_ex_flame_range) + +/datum/explosion_data/proc/create_effect(smoke) + if(heavy_impact_range > 1) + var/datum/effect_system/explosion/E + if(smoke) + E = new /datum/effect_system/explosion/smoke + else + E = new + E.set_up(epicenter) + E.start() + +/datum/explosion_data/proc/enqueue_affected_turfs(multiz_explosions, reactionary_explosions) + var/list/affected_turfs = prepare_explosion_turfs(max_range, epicenter) + if(multiz_explosions) + get_multiz_affect(affected_turfs) + + if(reactionary_explosions) + count_reactionary_explosions(affected_turfs) + + for(var/turf in affected_turfs) + affected_turfs_queue.enqueue(turf) + +/datum/explosion_data/proc/get_multiz_affect(list/affected_turfs) + var/turf/above = GET_TURF_ABOVE(epicenter) + var/turf/below = GET_TURF_BELOW(epicenter) + floor_block["[z0]"] = epicenter.explosion_vertical_block + + //We check for multi-z here. So in the code below(readtional explosives), we don't need to care about checking for above or below. + if(above) + affected_turfs += prepare_explosion_turfs(max_range, above) + epicenter_list += above + floor_block["[above.z]"] = above.explosion_vertical_block + + if(below) + affected_turfs += prepare_explosion_turfs(max_range, below) + epicenter_list += below + floor_block["[below.z]"] = below.explosion_vertical_block + +/datum/explosion_data/proc/count_reactionary_explosions(list/affected_turfs) + for(var/turf/T as anything in affected_turfs) // we cache the explosion block rating of every turf in the explosion area + cached_exp_block[T] = 0 + if(T.density && T.explosion_block) + cached_exp_block[T] += T.explosion_block + + for(var/atom/O as anything in T) + var/the_block = O.explosion_block + cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + +/datum/explosion_data/proc/explosion_log(cause) + var/cause_str + var/atom/cause_atom + var/cause_vv = "" + if(isatom(cause)) + cause_atom = cause + cause_str = cause_atom.name + cause_vv += ADMIN_VV(cause_atom,"VV") + else if(istext(cause)) + cause_str = cause + else if(isnull(cause)) + pass() + else + log_runtime("Bad type of cause for logging explosion.") + + message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str] [cause_vv])" : ""] [ADMIN_VERBOSEJMP(epicenter)] ") + add_game_logs("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str])" : ""] [AREACOORD(epicenter)] ") + +/datum/explosion_data/proc/log_explosions_machines(took) + //Machines which report explosions. + for(var/array in GLOB.doppler_arrays) + if(!array) + continue + if(istype(array, /obj/machinery/doppler_array)) + var/obj/machinery/doppler_array/doppler_array = array + doppler_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) + if(istype(array, /obj/item/clothing/head/helmet/space/hardsuit/rd)) + var/obj/item/clothing/head/helmet/space/hardsuit/rd/helm_array = array + helm_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_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 +*/ +/datum/explosion_data/proc/play_sounds_and_shake() + var/frequency = get_rand_frequency() + var/on_station = is_station_level(epicenter.z) + var/creaking_explosion = FALSE + + if(prob(devastation_range * DEVASTATION_PROB + heavy_impact_range * HEAVY_IMPACT_PROB) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might. + creaking_explosion = TRUE // prob over 100 always returns true + + for(var/MN in GLOB.player_list) + var/mob/M = MN + // Double check for client + var/turf/M_turf = get_turf(M) + if(M_turf && M_turf.z == z0) + var/dist = get_dist(M_turf, epicenter) + var/baseshakeamount + if(orig_max_distance - dist > 0) + baseshakeamount = sqrt((orig_max_distance - dist) * 0.1) + // If inside the blast radius + world.view - 2 + if(dist <= round(max_range + world.view - 2, 1)) + M.playsound_local(epicenter, null, 100, 1, frequency, S = SSexplosions.explosion_sound) + if(baseshakeamount > 0) + shake_camera(M, 25, clamp(baseshakeamount, 0, 10)) + // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station. + else if(dist <= far_dist) + var/far_volume = clamp(far_dist / 2, FAR_LOWER, FAR_UPPER) // Volume is based on explosion size and dist + if(creaking_explosion) + M.playsound_local(epicenter, null, far_volume, 1, frequency, S = SSexplosions.creaking_explosion_sound, distance_multiplier = 0) + else if(prob(PROB_SOUND)) // Sound variety during meteor storm/tesloose/other bad event + M.playsound_local(epicenter, null, far_volume, 1, frequency, S = SSexplosions.far_explosion_sound, distance_multiplier = 0) // Far sound + else + M.playsound_local(epicenter, null, far_volume, 1, frequency, S = SSexplosions.explosion_echo_sound, distance_multiplier = 0) // Echo sound + + if(baseshakeamount > 0 || devastation_range) + if(!baseshakeamount) // Devastating explosions rock the station and ground + baseshakeamount = devastation_range * 3 + shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) + else if(!isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull + var/echo_volume = 40 + if(devastation_range) + baseshakeamount = devastation_range + shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) + echo_volume = 60 + M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = SSexplosions.explosion_echo_sound, distance_multiplier = 0) + + if(creaking_explosion) // 5 seconds after the bang, the station begins to creak + addtimer(CALLBACK(M, TYPE_PROC_REF(/mob, playsound_local), epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), 1, frequency, null, null, FALSE, SSexplosions.hull_creaking_sound, 0), CREAK_DELAY) + +/// Returns a list of turfs in X range from the epicenter +/// Returns in a unique order, spiraling outwards +/// This is done to ensure our progressive cache of blast resistance is always valid +/// This is quite fast +/proc/prepare_explosion_turfs(range, turf/epicenter, protect_epicenter, explosion_direction = 0, explosion_arc = 360) + var/list/outlist = list() + var/list/candidates = list() + + var/our_x = epicenter.x + var/our_y = epicenter.y + var/our_z = epicenter.z + + var/max_x = world.maxx + var/max_y = world.maxy + + // Work out the angles to explode between + var/first_angle_limit = WRAP(explosion_direction - explosion_arc * 0.5, 0, 360) + var/second_angle_limit = WRAP(explosion_direction + explosion_arc * 0.5, 0, 360) + + // Get everything in the right order + var/lower_angle_limit + var/upper_angle_limit + var/do_directional + var/reverse_angle + + // Work out which case we're in + if(first_angle_limit == second_angle_limit) // CASE A: FULL CIRCLE + do_directional = FALSE + else if(first_angle_limit < second_angle_limit) // CASE B: When the arc does not cross 0 degrees + lower_angle_limit = first_angle_limit + upper_angle_limit = second_angle_limit + do_directional = TRUE + reverse_angle = FALSE + else if (first_angle_limit > second_angle_limit) // CASE C: When the arc crosses 0 degrees + lower_angle_limit = second_angle_limit + upper_angle_limit = first_angle_limit + do_directional = TRUE + reverse_angle = TRUE + + if(!protect_epicenter) + if(!do_directional) + candidates += epicenter + else + outlist += epicenter + + for(var/i in 1 to range) + var/lowest_x = our_x - i + var/lowest_y = our_y - i + var/highest_x = our_x + i + var/highest_y = our_y + i + // top left to one before top right + if(highest_y <= max_y) + candidates += block(max(lowest_x, 1), highest_y, our_z, + min(highest_x - 1, max_x), highest_y, our_z) + // top right to one before bottom right + if(highest_x <= max_x) + candidates += block(highest_x, min(highest_y, max_y), our_z, + highest_x, max(lowest_y + 1, 1), our_z) + // bottom right to one before bottom left + if(lowest_y >= 1) + candidates += block(min(highest_x, max_x), lowest_y, our_z, + max(lowest_x + 1, 1), lowest_y, our_z) + // bottom left to one before top left + if(lowest_x >= 1) + candidates += block(lowest_x, max(lowest_y, 1), our_z, + lowest_x, min(highest_y - 1, max_y), our_z) + + if(!do_directional) + outlist = candidates + else + for(var/turf/candidate as anything in candidates) + var/angle = get_angle(epicenter, candidate) + if(ISINRANGE(angle, lower_angle_limit, upper_angle_limit) ^ reverse_angle) + outlist += candidate + return outlist + +#undef CREAK_DELAY +#undef DEVASTATION_PROB +#undef HEAVY_IMPACT_PROB +#undef FAR_UPPER +#undef FAR_LOWER +#undef PROB_SOUND +#undef SHAKE_CLAMP +#undef FREQ_UPPER +#undef FREQ_LOWER diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 266112c9f36..7a754e791d2 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1753,3 +1753,7 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) */ /atom/proc/relaydrive(mob/living/user, direction) return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE) + +///returns how much the object blocks an explosion. Used by subtypes. +/atom/proc/GetExplosionBlock() + CRASH("Unimplemented GetExplosionBlock()") diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 8f8d4991d87..453551832ce 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -10,252 +10,6 @@ #define FREQ_UPPER 40 //The upper limit for the randomly selected frequency. #define FREQ_LOWER 25 //The lower of the above. -/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, ignorecap = 0, flame_range = 0, silent = 0, smoke = 1, var/cause = null, breach = TRUE) - epicenter = get_turf(epicenter) - if(!epicenter) - return - - // Archive the uncapped explosion for the doppler array - var/orig_dev_range = devastation_range - var/orig_heavy_range = heavy_impact_range - var/orig_light_range = light_impact_range - - var/orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range) - - if(!ignorecap) - // Clamp all minimal values to 0, due to negative explosions doesn't exist - devastation_range = max(devastation_range, 0) - heavy_impact_range = max(heavy_impact_range, 0) - light_impact_range = max(light_impact_range, 0) - flash_range = max(flash_range, 0) - flame_range = max(flame_range, 0) - - // Clamp all values to MAX_EXPLOSION_RANGE - devastation_range = min (GLOB.max_ex_devastation_range, devastation_range) - heavy_impact_range = min (GLOB.max_ex_heavy_range, heavy_impact_range) - light_impact_range = min (GLOB.max_ex_light_range, light_impact_range) - flash_range = min (GLOB.max_ex_flash_range, flash_range) - flame_range = min (GLOB.max_ex_flame_range, flame_range) - - var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) - - spawn(0) - var/watch = start_watch() - - var/list/cached_exp_block = list() - var/multiz_explosions = FALSE - if(CONFIG_GET(flag/multiz_explosions)) - multiz_explosions = TRUE - var/reactionary_explosions = FALSE - if(CONFIG_GET(flag/reactionary_explosions)) - reactionary_explosions = TRUE - - if(adminlog) - var/cause_str - var/atom/cause_atom - var/cause_vv = "" - if(isatom(cause)) - cause_atom = cause - cause_str = cause_atom.name - cause_vv += ADMIN_VV(cause_atom,"VV") - else if(istext(cause)) - cause_str = cause - else if(isnull(cause)) - pass() - else - log_runtime("Bad type of cause for logging explosion.") - - message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str] [cause_vv])" : ""] [ADMIN_VERBOSEJMP(epicenter)] ") - add_game_logs("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) [cause ? "(Cause: [cause_str])" : ""] [AREACOORD(epicenter)] ") - - var/x0 = epicenter.x - var/y0 = epicenter.y - var/z0 = epicenter.z - - // 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 * 15 - far_dist += devastation_range * 20 - - if(!silent) - var/frequency = get_rand_frequency() - var/sound/explosion_sound = sound(get_sfx("explosion")) - var/sound/far_explosion_sound = sound('sound/effects/explosionfar.ogg') - var/sound/creaking_explosion_sound = sound(get_sfx("explosion_creaking")) - var/sound/hull_creaking_sound = sound(get_sfx("hull_creaking")) - var/sound/explosion_echo_sound = sound('sound/effects/explosion_distant.ogg') - var/on_station = is_station_level(epicenter.z) - var/creaking_explosion = FALSE - - if(prob(devastation_range * DEVASTATION_PROB + heavy_impact_range * HEAVY_IMPACT_PROB) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might. - creaking_explosion = TRUE // prob over 100 always returns true - - for(var/MN in GLOB.player_list) - var/mob/M = MN - // Double check for client - var/turf/M_turf = get_turf(M) - if(M_turf && M_turf.z == z0) - var/dist = get_dist(M_turf, epicenter) - var/baseshakeamount - if(orig_max_distance - dist > 0) - baseshakeamount = sqrt((orig_max_distance - dist) * 0.1) - // If inside the blast radius + world.view - 2 - if(dist <= round(max_range + world.view - 2, 1)) - M.playsound_local(epicenter, null, 100, 1, frequency, S = explosion_sound) - if(baseshakeamount > 0) - shake_camera(M, 25, clamp(baseshakeamount, 0, 10)) - // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station. - else if(dist <= far_dist) - var/far_volume = clamp(far_dist / 2, FAR_LOWER, FAR_UPPER) // Volume is based on explosion size and dist - if(creaking_explosion) - M.playsound_local(epicenter, null, far_volume, 1, frequency, S = creaking_explosion_sound, distance_multiplier = 0) - else if(prob(PROB_SOUND)) // Sound variety during meteor storm/tesloose/other bad event - M.playsound_local(epicenter, null, far_volume, 1, frequency, S = far_explosion_sound, distance_multiplier = 0) // Far sound - else - M.playsound_local(epicenter, null, far_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0) // Echo sound - - if(baseshakeamount > 0 || devastation_range) - if(!baseshakeamount) // Devastating explosions rock the station and ground - baseshakeamount = devastation_range * 3 - shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) - else if(!isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull - var/echo_volume = 40 - if(devastation_range) - baseshakeamount = devastation_range - shake_camera(M, 10, clamp(baseshakeamount * 0.25, 0, SHAKE_CLAMP)) - echo_volume = 60 - M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0) - - if(creaking_explosion) // 5 seconds after the bang, the station begins to creak - addtimer(CALLBACK(M, TYPE_PROC_REF(/mob, playsound_local), epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), 1, frequency, null, null, FALSE, hull_creaking_sound, 0), CREAK_DELAY) - - if(heavy_impact_range > 1) - var/datum/effect_system/explosion/E - if(smoke) - E = new /datum/effect_system/explosion/smoke - else - E = new - E.set_up(epicenter) - E.start() - - var/list/affected_turfs = spiral_range_turfs(max_range, epicenter) - var/list/epicenter_list = list(epicenter) - var/list/floor_block = list() // [z] = num_block - if(multiz_explosions) - var/turf/above = GET_TURF_ABOVE(epicenter) - var/turf/below = GET_TURF_BELOW(epicenter) - floor_block["[z0]"] = epicenter.explosion_vertical_block - - //We check for multi-z here. So in the code below(readtional explosives), we don't need to care about checking for above or below. - if(above) - affected_turfs += spiral_range_turfs(max_range, above) - epicenter_list += above - floor_block["[above.z]"] = above.explosion_vertical_block - - if(below) - affected_turfs += spiral_range_turfs(max_range, below) - epicenter_list += below - floor_block["[below.z]"] = below.explosion_vertical_block - - if(reactionary_explosions) - for(var/A in affected_turfs) // we cache the explosion block rating of every turf in the explosion area - var/turf/T = A - cached_exp_block[T] = 0 - if(T.density && T.explosion_block) - cached_exp_block[T] += T.explosion_block - - for(var/obj/O in T) - var/the_block = O.explosion_block - cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block - CHECK_TICK - - for(var/A in affected_turfs) - var/turf/T = A - if(!T) - continue - var/dist = HYPOTENUSE(T.x, T.y, x0, y0) - if(multiz_explosions && T.z != z0) - if(T.z < z0) // we hit the turf that is below our epicenter. Check epicenter turf - dist += floor_block["[T.z + 1]"] + 1 //cheapest way to implement hypotenuse with z coordinates - else - dist += floor_block["[T.z]"] + 1 - - - if(reactionary_explosions) - var/turf/Trajectory = T - while(!(Trajectory in epicenter_list)) - Trajectory = get_step_towards(Trajectory, epicenter) - dist += cached_exp_block[Trajectory] - - var/flame_dist = 0 -// var/throw_dist = max_range - dist - - if(dist < flame_range) - flame_dist = 1 - - if(dist < devastation_range) dist = 1 - else if(dist < heavy_impact_range) dist = 2 - else if(dist < light_impact_range) dist = 3 - else dist = 0 - - //------- TURF FIRES ------- - - if(T) - if(flame_dist && prob(40) && !isspaceturf(T) && !T.density) - new /obj/effect/hotspot(T) //Mostly for ambience! - if(dist > 0) - if(issimulatedturf(T)) - var/turf/simulated/S = T - var/affecting_level - if(dist == 1) - affecting_level = 1 - else - affecting_level = S.is_shielded() ? 2 : (S.intact ? 2 : 1) - for(var/atom in S.contents) //bypass type checking since only atom can be contained by turfs anyway - var/atom/AM = atom - if(!QDELETED(AM) && AM.simulated) - if(AM.level >= affecting_level) - AM.ex_act(dist, epicenter) - else - for(var/atom in T.contents) //see above - var/atom/AM = atom - if(!QDELETED(AM) && AM.simulated) - AM.ex_act(dist, epicenter) - CHECK_TICK - if(breach) - T.ex_act(dist, epicenter) - else - T.ex_act(3, epicenter) - - CHECK_TICK - - var/took = stop_watch(watch) - //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare - log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.") - - //Machines which report explosions. - for(var/array in GLOB.doppler_arrays) - if(!array) - continue - if(istype(array, /obj/machinery/doppler_array)) - var/obj/machinery/doppler_array/doppler_array = array - doppler_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) - if(istype(array, /obj/item/clothing/head/helmet/space/hardsuit/rd)) - var/obj/item/clothing/head/helmet/space/hardsuit/rd/helm_array = array - helm_array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) - return 1 - - - -/proc/secondaryexplosion(turf/epicenter, range) - for(var/turf/tile in spiral_range_turfs(range, epicenter)) - tile.ex_act(2, epicenter) - /client/proc/check_bomb_impacts() set name = "Check Bomb Impact" set category = "Debug" diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 094dc3a3d10..75905e6c15a 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -310,6 +310,3 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e var/mob/living/buckled_mob = m buckled_mob.electrocute_act((clamp(round(strength * 1.25e-3), 10, 90) + rand(-5, 5)), src, flags = SHOCK_TESLA) -///returns how much the object blocks an explosion. Used by subtypes. -/obj/proc/GetExplosionBlock() - CRASH("Unimplemented GetExplosionBlock()") diff --git a/paradise.dme b/paradise.dme index 80da0e922d1..07df95d9d2b 100644 --- a/paradise.dme +++ b/paradise.dme @@ -219,6 +219,8 @@ #include "code\__HELPERS\unsorted.dm" #include "code\__HELPERS\verb_helpers.dm" #include "code\__HELPERS\visual_effects.dm" +#include "code\__HELPERS\data_struct\priority_queue.dm" +#include "code\__HELPERS\data_struct\queue.dm" #include "code\__HELPERS\paths\jps.dm" #include "code\__HELPERS\paths\path.dm" #include "code\__HELPERS\paths\sssp.dm" @@ -329,6 +331,7 @@ #include "code\controllers\subsystem\demo.dm" #include "code\controllers\subsystem\early_assets.dm" #include "code\controllers\subsystem\events.dm" +#include "code\controllers\subsystem\explosions.dm" #include "code\controllers\subsystem\fires.dm" #include "code\controllers\subsystem\fluids.dm" #include "code\controllers\subsystem\game_events.dm" From 97e6e3e28f432f8cc29579e126d6a0fc98364884 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:53:06 +0500 Subject: [PATCH 06/11] style fix --- code/__HELPERS/data_struct/priority_queue.dm | 19 ++++++++++--------- code/controllers/subsystem/explosions.dm | 2 +- code/game/atoms.dm | 4 ++-- code/game/machinery/doors/door.dm | 2 +- code/game/objects/explosion.dm | 2 +- code/game/objects/structures/window.dm | 2 +- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm index fad1b433c6f..b41bc092edf 100644 --- a/code/__HELPERS/data_struct/priority_queue.dm +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -34,17 +34,18 @@ break /priority_queue/proc/bubble_down(index) - while (index * 2 <= heap.len) - var/child = index * 2 + while(index * 2 <= heap.len) + var/child = index * 2 - if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) - child++ + if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) + child++ + + if (heap[child][1] >= heap[index][1]) + break + + swap(index, child) + index = child - if (heap[child][1] < heap[index][1]) - swap(index, child) - index = child - else - break /priority_queue/proc/swap(a, b) var/list/temp = heap[a] diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index 4984399bc07..ef01a937e4b 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -252,7 +252,7 @@ SUBSYSTEM_DEF(explosions) for(var/atom/O as anything in T) var/the_block = O.explosion_block - cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.get_explosion_block() : the_block /datum/explosion_data/proc/explosion_log(cause) var/cause_str diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 7a754e791d2..5ecf1acf0b2 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1755,5 +1755,5 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE) ///returns how much the object blocks an explosion. Used by subtypes. -/atom/proc/GetExplosionBlock() - CRASH("Unimplemented GetExplosionBlock()") +/atom/proc/get_explosion_block() + CRASH("Unimplemented get_explosion_block()") diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index a399d818477..058399d9bf2 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -528,5 +528,5 @@ ..(severity ? max(1, severity - 1) : 0) -/obj/machinery/door/GetExplosionBlock() +/obj/machinery/door/get_explosion_block() return density ? real_explosion_block : 0 diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 453551832ce..5e60931d995 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -84,7 +84,7 @@ for(var/obj/O in T) var/the_block = O.explosion_block - dist += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + dist += the_block == EXPLOSION_BLOCK_PROC ? O.get_explosion_block() : the_block if(dist < dev) T.color = "red" diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index ef7dfd250ba..4c15926b394 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -516,7 +516,7 @@ GLOBAL_LIST_INIT(wcCommon, pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e", C.throw_at(throwingdatum.initial_target, throwingdatum.maxrange - 1, throwingdatum.speed - 1) //Annnnnnnd yeet them into space, but slower, now that everything is dealt with -/obj/structure/window/GetExplosionBlock() +/obj/structure/window/get_explosion_block() return reinf && fulltile ? real_explosion_block : 0 /obj/structure/window/basic From cd2b7d163dcd1b65c021fbd702df04fb28297f13 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:00:01 +0500 Subject: [PATCH 07/11] fix --- code/__HELPERS/data_struct/priority_queue.dm | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm index b41bc092edf..b27b7796d9f 100644 --- a/code/__HELPERS/data_struct/priority_queue.dm +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -25,13 +25,15 @@ return heap.len == 0 /priority_queue/proc/bubble_up(index) - while (index > 1) - var/parent = round(index / 2) - if (heap[index][1] < heap[parent][1]) - swap(index, parent) - index = parent - else - break + while(index > 1) + var/parent = round(index / 2) + + if (heap[parent][1] < heap[index][1]) + break + + swap(index, parent) + index = parent + /priority_queue/proc/bubble_down(index) while(index * 2 <= heap.len) @@ -40,7 +42,7 @@ if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) child++ - if (heap[child][1] >= heap[index][1]) + if (heap[index][1] < heap[child][1]) break swap(index, child) From 95ba9371f4ad2efe4bcdbf09589ff9ccfae724f0 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Wed, 22 Jan 2025 05:12:24 +0500 Subject: [PATCH 08/11] infinity cycle fix --- code/__HELPERS/data_struct/priority_queue.dm | 52 +++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/code/__HELPERS/data_struct/priority_queue.dm b/code/__HELPERS/data_struct/priority_queue.dm index b27b7796d9f..f2a0808fb04 100644 --- a/code/__HELPERS/data_struct/priority_queue.dm +++ b/code/__HELPERS/data_struct/priority_queue.dm @@ -1,34 +1,48 @@ /priority_queue - var/list/heap = list() + var/list/priority_node/heap = list() -/priority_queue/proc/enqueue(value, priority) - heap += list(list(priority, value)) - bubble_up(heap.len) +/priority_node + var/item + var/priority -/priority_queue/proc/dequeue() - if (heap.len == 0) - return null +/priority_node/New(item, priority) + . = ..() + src.item = item + src.priority = priority - var/list/top = heap[1] - heap[1] = heap[heap.len] - heap.Cut(heap.len, heap.len) - bubble_down(1) +/priority_queue/proc/enqueue(value, priority) + heap += list(new /priority_node(value, priority)) + bubble_up(heap.len) - return top[2] +/priority_queue/proc/dequeue() + if (heap.len == 0) + return null + + var/priority_node/top = heap[1] + var/bottom = heap[heap.len] + var/item = top.item + heap -= bottom + if(!heap.len) + qdel(top) + return item + heap[1] = bottom + bubble_down(1) + qdel(top) + return item /priority_queue/proc/peek() - if (heap.len == 0) - return null - return heap[1][2] + if (heap.len == 0) + return null + return heap[1].item /priority_queue/proc/is_empty() - return heap.len == 0 + return heap.len == 0 /priority_queue/proc/bubble_up(index) while(index > 1) var/parent = round(index / 2) - if (heap[parent][1] < heap[index][1]) + if (heap[parent].priority < heap[index].priority) break swap(index, parent) @@ -39,10 +53,10 @@ while(index * 2 <= heap.len) var/child = index * 2 - if (child + 1 <= heap.len && heap[child + 1][1] < heap[child][1]) + if (child + 1 <= heap.len && heap[child + 1].priority < heap[child].priority) child++ - if (heap[index][1] < heap[child][1]) + if (heap[index].priority < heap[child].priority) break swap(index, child) From 5eda9211e941024e00d79dd6b65895828a52c998 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:19:02 +0500 Subject: [PATCH 09/11] node qdels fix --- code/__HELPERS/data_struct/queue.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/__HELPERS/data_struct/queue.dm b/code/__HELPERS/data_struct/queue.dm index 569cb875e9a..11f03adf0ff 100644 --- a/code/__HELPERS/data_struct/queue.dm +++ b/code/__HELPERS/data_struct/queue.dm @@ -48,6 +48,7 @@ else tail = null old_head.value = null + old_head.next = null qdel(old_head) count-- return value From e1033d0cceb95f12a6c599d554c7f1258261bcc4 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:43:49 +0500 Subject: [PATCH 10/11] no_more_infinity_cycle --- code/controllers/subsystem/explosions.dm | 46 +++++++++++++++++------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index ef01a937e4b..863b897a69a 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -47,7 +47,7 @@ SUBSYSTEM_DEF(explosions) return SS_INIT_SUCCESS -/datum/controller/subsystem/explosions/fire() +/datum/controller/subsystem/explosions/fire(resumed = 0) while(!explosion_queue.is_empty()) var/datum/explosion_data/data = explosion_queue.peek() while(!data.affected_turfs_queue.is_empty()) @@ -62,10 +62,16 @@ SUBSYSTEM_DEF(explosions) dist += data.floor_block["[T.z]"] + 1 if(reactionary_explosions) - var/turf/Trajectory = T - while(!(Trajectory in data.epicenter_list)) - Trajectory = get_step_towards(Trajectory, data.epicenter) - dist += data.cached_exp_block[Trajectory] + var/turf_block = data.cached_turf_exp_block[T] ? data.cached_turf_exp_block[T] : count_turf_block(T) + var/total_cords = "[T.x],[T.y],[T.z]" + if(T == data.epicenter) + data.cached_exp_block[total_cords] = turf_block + else + var/turf/next_turf = get_step_towards(T, data.epicenter) + var/next_cords = "[next_turf.x],[next_turf.y],[next_turf.z]" + var/prev_block = data.cached_exp_block[next_cords] ? data.cached_exp_block[next_cords] : count_turf_block(next_turf) + dist += prev_block + data.cached_exp_block[total_cords] = prev_block + turf_block var/flame_dist = 0 @@ -161,6 +167,7 @@ SUBSYSTEM_DEF(explosions) var/light_impact_range var/breach var/queue/affected_turfs_queue = new() + var/list/cached_turf_exp_block = list() var/list/cached_exp_block = list() var/list/epicenter_list = list() var/list/floor_block = list() @@ -198,6 +205,14 @@ SUBSYSTEM_DEF(explosions) /datum/explosion_data/Destroy() qdel(affected_turfs_queue) + LAZYCLEARLIST(epicenter_list) + LAZYNULL(epicenter_list) + LAZYCLEARLIST(cached_exp_block) + LAZYNULL(cached_exp_block) + LAZYCLEARLIST(cached_turf_exp_block) + LAZYNULL(cached_turf_exp_block) + LAZYCLEARLIST(floor_block) + LAZYNULL(floor_block ) . = ..() /datum/explosion_data/proc/clamp_ranges() @@ -207,6 +222,7 @@ SUBSYSTEM_DEF(explosions) flash_range = clamp(flash_range, 0, GLOB.max_ex_flash_range) flame_range = clamp(flame_range, 0, GLOB.max_ex_flame_range) + /datum/explosion_data/proc/create_effect(smoke) if(heavy_impact_range > 1) var/datum/effect_system/explosion/E @@ -245,14 +261,18 @@ SUBSYSTEM_DEF(explosions) floor_block["[below.z]"] = below.explosion_vertical_block /datum/explosion_data/proc/count_reactionary_explosions(list/affected_turfs) - for(var/turf/T as anything in affected_turfs) // we cache the explosion block rating of every turf in the explosion area - cached_exp_block[T] = 0 - if(T.density && T.explosion_block) - cached_exp_block[T] += T.explosion_block - - for(var/atom/O as anything in T) - var/the_block = O.explosion_block - cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.get_explosion_block() : the_block + for(var/turf/counted_turf as anything in affected_turfs) // we cache the explosion block rating of every turf in the explosion area + cached_turf_exp_block[counted_turf] = count_turf_block(counted_turf) + +/proc/count_turf_block(turf/counted_turf) + var/block = 0 + if(counted_turf.density && counted_turf.explosion_block) + block += counted_turf.explosion_block + + for(var/atom/object as anything in counted_turf) + var/the_block = object.explosion_block + block += the_block == EXPLOSION_BLOCK_PROC ? object.get_explosion_block() : the_block + return block /datum/explosion_data/proc/explosion_log(cause) var/cause_str From ac6e32d4e860a682f834ba9246361d4e63bbfcd3 Mon Sep 17 00:00:00 2001 From: dageavtobusnick <71216640+dageavtobusnick@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:17:03 +0500 Subject: [PATCH 11/11] update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена корректная более обработка мультиЗ взрывов, теперь взрыв на разны уровнях происходит в рамках одной обработки, а не как последовательность взрывов на разных уровнях. Так же в прок добавлена возможность содавать направленные взрывы, а так же исключать эпицентр из обработки. Убрана старая обработка мультиЗ взрывов --- code/controllers/subsystem/explosions.dm | 136 +++++++++++------- .../modules/space_management/multiz_helper.dm | 9 ++ 2 files changed, 93 insertions(+), 52 deletions(-) diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index 863b897a69a..157cf9f452f 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -55,23 +55,33 @@ SUBSYSTEM_DEF(explosions) if(QDELETED(T)) continue var/dist = HYPOTENUSE(T.x, T.y, data.x0, data.y0) - if(multiz_explosions && T.z != data.z0) - if(T.z < data.z0) // we hit the turf that is below our epicenter. Check epicenter turf - dist += data.floor_block["[T.z + 1]"] + 1 //cheapest way to implement hypotenuse with z coordinates - else - dist += data.floor_block["[T.z]"] + 1 if(reactionary_explosions) - var/turf_block = data.cached_turf_exp_block[T] ? data.cached_turf_exp_block[T] : count_turf_block(T) + var/turf_block var/total_cords = "[T.x],[T.y],[T.z]" + var/prev_block + if(data.multiz_explosion) + turf_block = data.cached_turf_vert_exp_block[T] ? data.cached_turf_vert_exp_block[T] : count_turf_vert_block(T) + if(T != data.epicenter) + var/turf/next_turf = get_step_towards_multiz(T, data.epicenter) + var/next_cords = "[next_turf.x],[next_turf.y],[next_turf.z]" + if(next_turf.z != T.z) + prev_block = data.cached_exp_block[next_cords] ? data.cached_exp_block[next_cords] : count_turf_vert_block(next_turf) + else + prev_block = data.cached_exp_block[next_cords] ? data.cached_exp_block[next_cords] : count_turf_block(next_turf) + + else + turf_block = data.cached_turf_exp_block[T] ? data.cached_turf_exp_block[T] : count_turf_block(T) + + if(T != data.epicenter) + var/turf/next_turf = get_step_towards(T, data.epicenter) + var/next_cords = "[next_turf.x],[next_turf.y],[next_turf.z]" + prev_block = data.cached_exp_block[next_cords] ? data.cached_exp_block[next_cords] : count_turf_block(next_turf) + if(T == data.epicenter) data.cached_exp_block[total_cords] = turf_block - else - var/turf/next_turf = get_step_towards(T, data.epicenter) - var/next_cords = "[next_turf.x],[next_turf.y],[next_turf.z]" - var/prev_block = data.cached_exp_block[next_cords] ? data.cached_exp_block[next_cords] : count_turf_block(next_turf) - dist += prev_block - data.cached_exp_block[total_cords] = prev_block + turf_block + dist += prev_block + data.cached_exp_block[total_cords] = prev_block + turf_block var/flame_dist = 0 @@ -124,21 +134,21 @@ SUBSYSTEM_DEF(explosions) if(!silent) data.play_sounds_and_shake() data.create_effect(smoke) - data.enqueue_affected_turfs(multiz_explosions, reactionary_explosions) + data.enqueue_affected_turfs(reactionary_explosions) explosion_queue.enqueue(data, data.affected_turfs_queue.count) -/datum/controller/subsystem/explosions/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0, silent = FALSE, smoke = TRUE, cause = null, breach = TRUE) +/datum/controller/subsystem/explosions/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0, silent = FALSE, smoke = TRUE, cause = null, breach = TRUE, protect_epicenter, explosion_direction, explosion_arc) if(!epicenter) return FALSE - var/datum/explosion_data/data = new(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap, flame_range, breach) + var/datum/explosion_data/data = new(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap, flame_range, breach, multiz_explosions, protect_epicenter, explosion_direction, explosion_arc) INVOKE_ASYNC(src, PROC_REF(start_explosion), data, adminlog, cause, smoke, silent) return TRUE -/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach) - SSexplosions.explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach) +/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach, protect_epicenter = FALSE, explosion_direction = 0, explosion_arc = 360) + SSexplosions.explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, cause, breach, protect_epicenter, explosion_direction, explosion_arc) /* * DON'T USE THIS!!! It is not processed by the system and has no radius restrictions. @@ -159,21 +169,27 @@ SUBSYSTEM_DEF(explosions) var/x0 var/y0 var/z0 + var/min_z + var/max_z var/far_dist = 0 var/flame_range var/flash_range var/devastation_range var/heavy_impact_range var/light_impact_range + var/explosion_direction = 0 + var/explosion_arc = 360 + var/protect_epicenter = FALSE var/breach + var/multiz_explosion = FALSE var/queue/affected_turfs_queue = new() var/list/cached_turf_exp_block = list() + var/list/cached_turf_vert_exp_block = list() var/list/cached_exp_block = list() var/list/epicenter_list = list() - var/list/floor_block = list() var/watch -/datum/explosion_data/New(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap = FALSE, flame_range = 0, breach = TRUE) +/datum/explosion_data/New(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, ignorecap = FALSE, flame_range = 0, breach = TRUE, multiz = FALSE, protect_epicenter = FALSE, explosion_direction = 0, explosion_arc = 360) . = ..() src.epicenter = get_turf(epicenter) src.flame_range = flame_range @@ -183,6 +199,9 @@ SUBSYSTEM_DEF(explosions) src.light_impact_range = light_impact_range src.breach = breach src.max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) + src.explosion_arc = explosion_arc + src.explosion_direction = explosion_direction + src.protect_epicenter = protect_epicenter orig_dev_range = devastation_range orig_heavy_range = heavy_impact_range @@ -190,6 +209,13 @@ SUBSYSTEM_DEF(explosions) orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range) + if(multiz) + var/turf/top_turf = get_highest_turf(epicenter) + var/turf/low_turf = get_lowest_turf(epicenter) + max_z = min(top_turf.z, epicenter.z + orig_max_distance) + min_z = max(low_turf.z, epicenter.z - orig_max_distance) + multiz_explosion = multiz && max_z != min_z + x0 = epicenter.x y0 = epicenter.y z0 = epicenter.z @@ -197,7 +223,6 @@ SUBSYSTEM_DEF(explosions) far_dist = 0 far_dist += heavy_impact_range * 15 far_dist += devastation_range * 20 - if(!ignorecap) clamp_ranges() epicenter_list += epicenter @@ -211,8 +236,8 @@ SUBSYSTEM_DEF(explosions) LAZYNULL(cached_exp_block) LAZYCLEARLIST(cached_turf_exp_block) LAZYNULL(cached_turf_exp_block) - LAZYCLEARLIST(floor_block) - LAZYNULL(floor_block ) + LAZYCLEARLIST(cached_turf_vert_exp_block) + LAZYNULL(cached_turf_vert_exp_block) . = ..() /datum/explosion_data/proc/clamp_ranges() @@ -233,36 +258,19 @@ SUBSYSTEM_DEF(explosions) E.set_up(epicenter) E.start() -/datum/explosion_data/proc/enqueue_affected_turfs(multiz_explosions, reactionary_explosions) - var/list/affected_turfs = prepare_explosion_turfs(max_range, epicenter) - if(multiz_explosions) - get_multiz_affect(affected_turfs) - +/datum/explosion_data/proc/enqueue_affected_turfs(reactionary_explosions) + var/list/affected_turfs = prepare_explosion_turfs(max_range, epicenter, protect_epicenter, explosion_direction, explosion_arc, multiz_explosion, min_z, max_z) if(reactionary_explosions) count_reactionary_explosions(affected_turfs) for(var/turf in affected_turfs) affected_turfs_queue.enqueue(turf) -/datum/explosion_data/proc/get_multiz_affect(list/affected_turfs) - var/turf/above = GET_TURF_ABOVE(epicenter) - var/turf/below = GET_TURF_BELOW(epicenter) - floor_block["[z0]"] = epicenter.explosion_vertical_block - - //We check for multi-z here. So in the code below(readtional explosives), we don't need to care about checking for above or below. - if(above) - affected_turfs += prepare_explosion_turfs(max_range, above) - epicenter_list += above - floor_block["[above.z]"] = above.explosion_vertical_block - - if(below) - affected_turfs += prepare_explosion_turfs(max_range, below) - epicenter_list += below - floor_block["[below.z]"] = below.explosion_vertical_block - /datum/explosion_data/proc/count_reactionary_explosions(list/affected_turfs) for(var/turf/counted_turf as anything in affected_turfs) // we cache the explosion block rating of every turf in the explosion area cached_turf_exp_block[counted_turf] = count_turf_block(counted_turf) + if(multiz_explosion) + cached_turf_vert_exp_block[counted_turf] = count_turf_vert_block(counted_turf) /proc/count_turf_block(turf/counted_turf) var/block = 0 @@ -274,6 +282,15 @@ SUBSYSTEM_DEF(explosions) block += the_block == EXPLOSION_BLOCK_PROC ? object.get_explosion_block() : the_block return block +/proc/count_turf_vert_block(turf/counted_turf) + var/block = 0 + if(counted_turf.density && counted_turf.explosion_block) + block += counted_turf.explosion_vertical_block + + for(var/atom/object as anything in counted_turf) + block += object.explosion_vertical_block + return block + /datum/explosion_data/proc/explosion_log(cause) var/cause_str var/atom/cause_atom @@ -361,7 +378,7 @@ SUBSYSTEM_DEF(explosions) /// Returns in a unique order, spiraling outwards /// This is done to ensure our progressive cache of blast resistance is always valid /// This is quite fast -/proc/prepare_explosion_turfs(range, turf/epicenter, protect_epicenter, explosion_direction = 0, explosion_arc = 360) +/proc/prepare_explosion_turfs(range, turf/epicenter, protect_epicenter, explosion_direction = 0, explosion_arc = 360, multiz = FALSE, min_z, max_z) var/list/outlist = list() var/list/candidates = list() @@ -369,6 +386,10 @@ SUBSYSTEM_DEF(explosions) var/our_y = epicenter.y var/our_z = epicenter.z + if(!multiz) + min_z = our_z + max_z = our_z + var/max_x = world.maxx var/max_y = world.maxy @@ -405,24 +426,35 @@ SUBSYSTEM_DEF(explosions) for(var/i in 1 to range) var/lowest_x = our_x - i var/lowest_y = our_y - i + var/lowest_z = our_z - i var/highest_x = our_x + i var/highest_y = our_y + i + var/highest_z = our_z + i // top left to one before top right if(highest_y <= max_y) - candidates += block(max(lowest_x, 1), highest_y, our_z, - min(highest_x - 1, max_x), highest_y, our_z) + candidates += block(lowest_x, highest_y, min_z, + highest_x - 1, highest_y, max_z) // top right to one before bottom right if(highest_x <= max_x) - candidates += block(highest_x, min(highest_y, max_y), our_z, - highest_x, max(lowest_y + 1, 1), our_z) + candidates += block(highest_x, highest_y, min_z, + highest_x, lowest_y + 1, max_z) + + if(multiz && highest_z <= max_z) + candidates += block(lowest_x + 1, highest_y - 1, max_z, + highest_x - 1, lowest_y + 1, max_z) + // bottom right to one before bottom left if(lowest_y >= 1) - candidates += block(min(highest_x, max_x), lowest_y, our_z, - max(lowest_x + 1, 1), lowest_y, our_z) + candidates += block(highest_x, lowest_y, min_z, + lowest_x + 1, lowest_y, max_z) // bottom left to one before top left if(lowest_x >= 1) - candidates += block(lowest_x, max(lowest_y, 1), our_z, - lowest_x, min(highest_y - 1, max_y), our_z) + candidates += block(lowest_x, lowest_y, min_z, + lowest_x, highest_y - 1, max_z) + + if(multiz && lowest_z >= min_z) + candidates += block(lowest_x + 1, highest_y - 1, min_z, + highest_x - 1, lowest_y + 1, max_z) if(!do_directional) outlist = candidates diff --git a/code/modules/space_management/multiz_helper.dm b/code/modules/space_management/multiz_helper.dm index 54a7440640f..387b28a059c 100644 --- a/code/modules/space_management/multiz_helper.dm +++ b/code/modules/space_management/multiz_helper.dm @@ -11,6 +11,15 @@ return get_step(GET_TURF_BELOW(us), dir) return get_step(ref, dir) +/proc/get_step_towards_multiz(atom/start, atom/target) + if(target.z == start.z) + return get_step_towards(start, target) + var/turf/start_turf = get_turf(start) + if(target.z > start.z) + return GET_TURF_ABOVE(start_turf) + return GET_TURF_BELOW(start_turf) + + /proc/get_dir_multiz(turf/us, turf/them) us = get_turf(us) them = get_turf(them)