Refactors explosions (#26361)

This commit is contained in:
Jordan Brown
2017-05-24 17:44:08 -04:00
committed by Leo
parent 66e881226e
commit 7b3bc38351
4 changed files with 194 additions and 90 deletions

View File

@@ -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

View File

@@ -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()
E = new
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
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"

View File

@@ -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)

View File

@@ -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"