From 7b3bc38351f6da6ff3fd754bae3df1e5a0e7e703 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 24 May 2017 17:44:08 -0400 Subject: [PATCH] Refactors explosions (#26361) --- code/__HELPERS/unsorted.dm | 15 +- code/{game/objects => datums}/explosion.dm | 266 ++++++++++++++------- code/game/atoms.dm | 1 - tgstation.dme | 2 +- 4 files changed, 194 insertions(+), 90 deletions(-) rename code/{game/objects => datums}/explosion.dm (52%) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 4eddd9aeea9c..c9f8329c5386 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1079,18 +1079,17 @@ B --><-- A return L //similar function to RANGE_TURFS(), but will search spiralling outwards from the center (like the above, but only turfs) -/proc/spiral_range_turfs(dist=0, center=usr, orange=0) +/proc/spiral_range_turfs(dist=0, center=usr, orange=0, list/outlist = list(), tick_checked) + outlist.Cut() if(!dist) - if(!orange) - return list(center) - else - return list() + outlist += center + return outlist var/turf/t_center = get_turf(center) if(!t_center) - return list() + return outlist - var/list/L = list() + var/list/L = outlist var/turf/T var/y var/x @@ -1128,6 +1127,8 @@ B --><-- A if(T) L += T c_dist++ + if(tick_checked) + CHECK_TICK return L diff --git a/code/game/objects/explosion.dm b/code/datums/explosion.dm similarity index 52% rename from code/game/objects/explosion.dm rename to code/datums/explosion.dm index 11079bf847e2..24422b86c6ca 100644 --- a/code/game/objects/explosion.dm +++ b/code/datums/explosion.dm @@ -1,10 +1,48 @@ -/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, ignorecap = 0, flame_range, silent = 0, smoke = 1) - set waitfor = 0 - src = null //so we don't abort once src is deleted +#define EXPLOSION_THROW_SPEED 4 + +GLOBAL_LIST_EMPTY(explosions) +//Against my better judgement, I will return the explosion datum +//If I see any GC errors for it I will find you +//and I will gib you +/proc/explosion(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0 , silent = FALSE, smoke = FALSE) + return new /datum/explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke) + +//This datum creates 3 async tasks +//1 GatherSpiralTurfsProc runs spiral_range_turfs(tick_checked = TRUE) to populate the affected_turfs list +//2 CaculateExplosionBlock adds the blockings to the cached_exp_block list +//3 The main thread explodes the prepared turfs + +/datum/explosion + var/explosion_id + var/started_at + var/running = TRUE + var/stopped = 0 //This is the number of threads stopped !DOESN'T COUNT THREAD 2! + var/static/id_counter = 0 + +#define EX_PREPROCESS_EXIT_CHECK \ + if(!running) {\ + stopped = 2;\ + qdel(src);\ + return;\ + } + +#define EX_PREPROCESS_CHECK_TICK \ + if(TICK_CHECK) {\ + stoplag();\ + EX_PREPROCESS_EXIT_CHECK\ + } + +/datum/explosion/New(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke) + set waitfor = FALSE + + var/id = ++id_counter + explosion_id = id + epicenter = get_turf(epicenter) if(!epicenter) return + GLOB.explosions += src if(isnull(flame_range)) flame_range = light_impact_range if(isnull(flash_range)) @@ -30,14 +68,13 @@ //I would make this not ex_act the thing that triggered the explosion, //but everything that explodes gives us their loc or a get_turf() //and somethings expect us to ex_act them so they can qdel() - sleep(1) //tldr, let the calling proc call qdel(src) before we explode + stoplag() //tldr, let the calling proc call qdel(src) before we explode - var/static/explosionid = 1 - var/id = explosionid++ - var/start = world.timeofday + EX_PREPROCESS_EXIT_CHECK + + started_at = REALTIMEOFDAY var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) - var/list/cached_exp_block = list() if(adminlog) message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area: [get_area(epicenter)] [ADMIN_COORDJMP(epicenter)]") @@ -53,23 +90,27 @@ far_dist += heavy_impact_range * 5 far_dist += devastation_range * 20 + var/x0 = epicenter.x + var/y0 = epicenter.y + var/z0 = epicenter.z + if(!silent) var/frequency = get_rand_frequency() var/ex_sound = get_sfx("explosion") for(var/mob/M in GLOB.player_list) // Double check for client - if(M && M.client) - var/turf/M_turf = get_turf(M) - if(M_turf && M_turf.z == epicenter.z) - var/dist = get_dist(M_turf, epicenter) - // If inside the blast radius + world.view - 2 - if(dist <= round(max_range + world.view - 2, 1)) - M.playsound_local(epicenter, ex_sound, 100, 1, frequency, falloff = 5) - // 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, 30, 50) // Volume is based on explosion size and dist - far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion - M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', far_volume, 1, frequency, falloff = 5) + var/turf/M_turf = get_turf(M) + if(M_turf && M_turf.z == z0) + var/dist = get_dist(M_turf, epicenter) + // If inside the blast radius + world.view - 2 + if(dist <= round(max_range + world.view - 2, 1)) + M.playsound_local(epicenter, ex_sound, 100, 1, frequency, falloff = 5) + // 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, 30, 50) // Volume is based on explosion size and dist + far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion + M.playsound_local(epicenter, 'sound/effects/explosionfar.ogg', far_volume, 1, frequency, falloff = 5) + EX_PREPROCESS_CHECK_TICK //postpone processing for a bit var/postponeCycles = max(round(devastation_range/8),1) @@ -77,66 +118,52 @@ SSmachines.postpone(postponeCycles) if(heavy_impact_range > 1) + var/datum/effect_system/explosion/E if(smoke) - var/datum/effect_system/explosion/smoke/E = new/datum/effect_system/explosion/smoke() - E.set_up(epicenter) - E.start() + E = new /datum/effect_system/explosion/smoke else - var/datum/effect_system/explosion/E = new/datum/effect_system/explosion() - E.set_up(epicenter) - E.start() - - var/x0 = epicenter.x - var/y0 = epicenter.y - var/z0 = epicenter.z - - var/list/affected_turfs = spiral_range_turfs(max_range, epicenter) - - if(config.reactionary_explosions) - for(var/turf/T 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/obj/machinery/door/D in T) - if(D.density && D.explosion_block) - cached_exp_block[T] += D.explosion_block - - for(var/obj/structure/window/W in T) - if(W.reinf && W.fulltile) - cached_exp_block[T] += W.explosion_block - - for(var/obj/structure/blob/B in T) - cached_exp_block[T] += B.explosion_block - CHECK_TICK + E = new + E.set_up(epicenter) + E.start() + + EX_PREPROCESS_CHECK_TICK //flash mobs if(flash_range) for(var/mob/living/L in viewers(flash_range, epicenter)) L.flash_act() - CHECK_TICK + EX_PREPROCESS_CHECK_TICK var/list/exploded_this_tick = list() //open turfs that need to be blocked off while we sleep - for(var/turf/T in affected_turfs) + var/list/affected_turfs = GatherSpiralTurfs(max_range, epicenter) - if (!T) - continue + var/reactionary = config.reactionary_explosions + var/list/cached_exp_block + + if(reactionary) + cached_exp_block = CaculateExplosionBlock(affected_turfs) + + //lists are guaranteed to contain at least 1 turf at this point + + var/iteration = 0 + var/affTurfLen = affected_turfs.len + var/expBlockLen = cached_exp_block.len + for(var/TI in affected_turfs) + var/turf/T = TI + ++iteration var/init_dist = cheap_hypotenuse(T.x, T.y, x0, y0) var/dist = init_dist - if(config.reactionary_explosions) + if(reactionary) var/turf/Trajectory = T while(Trajectory != epicenter) Trajectory = get_step_towards(Trajectory, epicenter) dist += cached_exp_block[Trajectory] - var/flame_dist = 0 + var/flame_dist = dist < flame_range var/throw_dist = dist - if(dist < flame_range) - flame_dist = 1 - if(dist < devastation_range) dist = 1 else if(dist < heavy_impact_range) @@ -148,27 +175,61 @@ //------- EX_ACT AND TURF FIRES ------- - if(T) - if(flame_dist && prob(40) && !isspaceturf(T) && !T.density) - new /obj/effect/hotspot(T) //Mostly for ambience! - if(dist > 0) - T.explosion_level = max(T.explosion_level, dist) //let the bigger one have it - T.explosion_id = id - T.ex_act(dist) - exploded_this_tick += T + if(flame_dist && prob(40) && !isspaceturf(T) && !T.density) + new /obj/effect/hotspot(T) //Mostly for ambience! + + if(dist > 0) + T.explosion_level = max(T.explosion_level, dist) //let the bigger one have it + T.explosion_id = id + T.ex_act(dist) + exploded_this_tick += T //--- THROW ITEMS AROUND --- var/throw_dir = get_dir(epicenter,T) for(var/obj/item/I in T) - if(I && !I.anchored) + if(!I.anchored) var/throw_range = rand(throw_dist, max_range) var/turf/throw_at = get_ranged_target_turf(I, throw_dir, throw_range) - I.throw_speed = 4 //Temporarily change their throw_speed for embedding purposes (Reset when it finishes throwing, regardless of hitting anything) - I.throw_at(throw_at, throw_range, I.throw_speed) + I.throw_speed = EXPLOSION_THROW_SPEED //Temporarily change their throw_speed for embedding purposes (Reset when it finishes throwing, regardless of hitting anything) + I.throw_at(throw_at, throw_range, EXPLOSION_THROW_SPEED) - if(TICK_CHECK) + //wait for the lists to repop + var/break_condition + if(reactionary) + //If we've caught up to the density checker thread and there are no more turfs to process + break_condition = iteration == expBlockLen && iteration < affTurfLen + else + //If we've caught up to the turf gathering thread and it's still running + break_condition = iteration == affTurfLen && !stopped + + if(break_condition || TICK_CHECK) stoplag() + + if(!running) + break + + //update the trackers + affTurfLen = affected_turfs.len + expBlockLen = cached_exp_block.len + + if(break_condition) + if(reactionary) + //until there are more block checked turfs than what we are currently at + //or the explosion has stopped + UNTIL(iteration < affTurfLen || !running) + else + //until there are more gathered turfs than what we are currently at + //or there are no more turfs to gather/the explosion has stopped + UNTIL(iteration < expBlockLen || stopped) + + if(!running) + break + + //update the trackers + affTurfLen = affected_turfs.len + expBlockLen = cached_exp_block.len + var/circumference = (PI * (init_dist + 4) * 2) //+4 to radius to prevent shit gaps if(exploded_this_tick.len > circumference) //only do this every revolution for(var/Unexplode in exploded_this_tick) @@ -182,24 +243,67 @@ UnexplodeT.explosion_level = 0 exploded_this_tick.Cut() - var/took = (world.timeofday-start)/10 - //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 + var/took = (REALTIMEOFDAY - started_at) / 10 + + //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 if(GLOB.Debug2) 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) - var/obj/machinery/doppler_array/A = array - A.sense_explosion(epicenter,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) + if(!stopped) //if we aren't in a hurry + //Machines which report explosions. + for(var/array in GLOB.doppler_arrays) + var/obj/machinery/doppler_array/A = array + A.sense_explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, took,orig_dev_range, orig_heavy_range, orig_light_range) - return 1 + ++stopped + qdel(src) +#undef EX_PREPROCESS_EXIT_CHECK +#undef EX_PREPROCESS_CHECK_TICK +//asyncly populate the affected_turfs list +/datum/explosion/proc/GatherSpiralTurfs(range, turf/epicenter) + set waitfor = FALSE + . = list() + spiral_range_turfs(range, epicenter, outlist = ., tick_checked = TRUE) + ++stopped -/proc/secondaryexplosion(turf/epicenter, range) - for(var/turf/tile in spiral_range_turfs(range, epicenter)) - tile.ex_act(2) +/datum/explosion/proc/CaculateExplosionBlock(list/affected_turfs) + set waitfor = FALSE + . = list() + var/processed = 0 + while(!stopped && running) + var/I + for(I in (processed + 1) to affected_turfs.len) // we cache the explosion block rating of every turf in the explosion area + var/turf/T = affected_turfs[I] + var/current_exp_block = T.density ? T.explosion_block : 0 + + for(var/obj/machinery/door/D in T) + if(D.density) + current_exp_block += D.explosion_block + + for(var/obj/structure/window/W in T) + if(W.reinf && W.fulltile) + current_exp_block += W.explosion_block + + for(var/obj/structure/blob/B in T) + current_exp_block += B.explosion_block + + .[T] = current_exp_block + + if(TICK_CHECK) + break + + processed = I + stoplag() + +/datum/explosion/Destroy() + running = FALSE + if(stopped < 2) //wait for main thread and spiral_range thread + return QDEL_HINT_IWILLGC + GLOB.explosions -= src + return ..() /client/proc/check_bomb_impacts() set name = "Check Bomb Impact" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 46583cf59238..7761fe2365f6 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -274,7 +274,6 @@ return /atom/proc/ex_act(severity, target) - set waitfor = FALSE contents_explosion(severity, target) /atom/proc/blob_act(obj/structure/blob/B) diff --git a/tgstation.dme b/tgstation.dme index 4219c0a7d988..9193bb24f3ee 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -217,6 +217,7 @@ #include "code\datums\dna.dm" #include "code\datums\dog_fashion.dm" #include "code\datums\emotes.dm" +#include "code\datums\explosion.dm" #include "code\datums\forced_movement.dm" #include "code\datums\holocall.dm" #include "code\datums\hud.dm" @@ -655,7 +656,6 @@ #include "code\game\mecha\working\working.dm" #include "code\game\objects\buckling.dm" #include "code\game\objects\empulse.dm" -#include "code\game\objects\explosion.dm" #include "code\game\objects\items.dm" #include "code\game\objects\obj_defense.dm" #include "code\game\objects\objs.dm"