Files
S.P.L.U.R.T-Station-13/code/datums/explosion2.dm
SandPoot b7a1e1e0b2 Upstream merge (#169)
* changes

* changes

* changes

* changes

* changes

* changes

* removes combat mode effects

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* less awful

* baton change

* whew

* okay

* math fix

* framework

* Oh Boy

* fixes

* conflict gone

* whew

* yell wip

* ripping off my own code

* fixes

* almost compiling

* e

* auto

* hacky

* local testing time

* modifications

* sigh

* Yelling works

* attempt examinate

* fix

* yelling

* ok

* it works

* it works.

* woo

* let's make this work

* Update PubbyStation.dmm

* Incinerator mapping tweak

* qm can announce to station

* blue airlocks (maybe will remove that)

* changes to HoP access

he couldn't access cargo already, now it's just polished so they don't have weird cargo access that they can't use

* changes Box HoP console to request only (like in the other maps)

* wtf why is this here

* and WHY is this here too???

* Revert "and WHY is this here too???"

This reverts commit 424a53773296eaf261c97992c618c580449f6031.

* Revert "wtf why is this here"

This reverts commit 59fdca38b55ce97ab5bdf28cbea7fb3b9915474e.

* Automatic changelog compile [ci skip]

* Automatic changelog compile [ci skip]

* Automatic changelog compile [ci skip]

* Automatic changelog compile [ci skip]

* Automatic changelog compile [ci skip]

* Automatic changelog compile [ci skip]

* Update PubbyStation.dmm

* Adds plushies from various sources/custom made.

A patch you didn't know you needed.

* More code. Shouldn't break anything.

We'll see if it does.

* Adds a rolled up newspaper sprite

* Adds a child item to the telebaton

identical stats for the future but different looks.

* And a recipe.

You attach a newspaper to a telebaton with tape.

* all the stuff to add pod to code

* The actual map

* Automatic changelog compile [ci skip]

* A

* Automatic changelog generation for PR #14822 [ci skip]

* Automatic changelog generation for PR #14895 [ci skip]

* Automatic changelog generation for PR #14861 [ci skip]

* Automatic changelog generation for PR #14893 [ci skip]

* Automatic changelog generation for PR #14899 [ci skip]

* Automatic changelog generation for PR #14883 [ci skip]

* Okay, let's try this again.

I don't like you, you don't like me,
can we please just get along?

* e

* Automatic changelog generation for PR #14904 [ci skip]

* Automatic changelog compile [ci skip]

Co-authored-by: silicons <2003111+silicons@users.noreply.github.com>
Co-authored-by: bunny232 <bunnyracer23@yahoo.com>
Co-authored-by: qweq12yt <45515587+qweq12yt@users.noreply.github.com>
Co-authored-by: Changelogs <action@github.com>
Co-authored-by: WanderingFox95 <75953558+WanderingFox95@users.noreply.github.com>
Co-authored-by: shellspeed1 <46614774+shellspeed1@users.noreply.github.com>
Co-authored-by: Lin <linzolle@gmail.com>
Co-authored-by: CitadelStationBot <citadelstationcommunity@gmail.com>
2021-06-30 12:12:52 -03:00

369 lines
13 KiB
Plaintext

/// Creates a wave explosion at a certain place
/proc/wave_explosion(turf/target, power, factor = EXPLOSION_DEFAULT_FALLOFF_MULTIPLY, constant = EXPLOSION_DEFAULT_FALLOFF_SUBTRACT, flash = 0, fire = 0, atom/source, speed = 0,
silent = FALSE, bypass_logging = FALSE, block_resistance = 1, start_immediately = TRUE)
if(!istype(target) || (power <= EXPLOSION_POWER_DEAD))
return
if(!bypass_logging)
var/logstring = "Wave explosion at [COORD(target)]: [power]/[factor]/[constant]/[flash]/[fire]/[speed] initial/factor/constant/flash/fire/speed"
log_game(logstring)
message_admins(logstring)
return new /datum/wave_explosion(target, power, factor, constant, flash, fire, source, speed, silent, start_immediately, block_resistance)
/**
* New force-blastwave explosion system
*/
/datum/wave_explosion
/// Next unique numerical ID
var/static/next_id = 0
/// Our unique nuumerical ID
var/id
/// world.time we started at
var/start_time
/// Are we currently running?
var/running = FALSE
/// Are we currently finished?
var/finished = FALSE
/// What atom we originated from, if any
var/atom/source
/// Explosion power at which point to consider to be a dead expansion
var/power_considered_dead = EXPLOSION_POWER_DEAD
/// Explosion power we were initially at
var/power_initial
/// Base explosion power falloff multiplier (applied first)
var/power_falloff_factor = EXPLOSION_DEFAULT_FALLOFF_MULTIPLY
/// Base explosion power falloff subtract (applied second)
var/power_falloff_constant = EXPLOSION_DEFAULT_FALLOFF_SUBTRACT
/// Flash range
var/flash_range = 0
/// Fire probability per tile
var/fire_probability = 0
/// Are we silent/do we make the screenshake/sounds?
var/silent = FALSE
// Modifications
/// Object damage mod
var/object_damage_mod = 1
/// Hard obstcales get this mod INSTEAD of object damage mod
var/hard_obstacle_mod = 1
/// Window shatter mod. Overrides both [hard_obstcale_mod] and [object_damage_mod]
var/window_shatter_mod = 1
/// Wall destruction mod
var/wall_destroy_mod = 1
/// Mob damage mod
var/mob_damage_mod = 1
/// Mob gib mod
var/mob_gib_mod = 1
/// Mob deafen mod
var/mob_deafen_mod = 1
/// block = block / this, if 0 any block is absolute
var/block_resistance = 1
// Rewrite count: 2
// Each cycle is a "perfect ring".
// We run into the problem that diagonal hitboxes don't exist on 2d grid games.
// How we deal with this is this:
// The first half of each cycle explodes cardinal directions awaiting expansion first
// Diagonals get added to a potential diagonals list.
// The second half of each cycle checks the potential diagonals list. If something isn't on the exploded list,
// we know it's a valid diagonal and explode it.
// Then all exploded turfs are flushed to exploded_last and it continues.
// Direction bitflags use the WEX_DIR_X flags so we can keep track of more than one direction in a single field
// The insanity begins when I realized that doing cardinals are easy but diagonals require:
// - Tallying the explosive power that should go into it
// - Exploding it afterwards using the tallied power rather than passed power (so corners aren't far weaker unless there's one side of it blocked)
// Expanding the explosion power of the now exploded diagonal into the two dirs its cardinals are in
// If this is done using a perfect algorithm it should be relatively efficient and result in a near-perfect shockwave simulation.
/// The last ring that's been exploded. Any turfs in this will completely ignore the current cycle. Turf = TRUE
var/list/turf/exploded_last = list()
/// The "edges" + dirs that need to be processed this cycle. turf = dir flags
var/list/turf/edges = list()
/// The powers of the current turf edges. turf = power
var/list/turf/powers = list()
/// What cycle are we on?
var/cycle
/// When we started the current cycle
var/cycle_start
/// Time to wait between cycles
var/cycle_speed = 0
/// Current index for list
var/index = 1
/datum/wave_explosion/New(turf/initial, power, factor = EXPLOSION_DEFAULT_FALLOFF_MULTIPLY, constant = EXPLOSION_DEFAULT_FALLOFF_SUBTRACT, flash = 0, fire = 0, atom/source, speed = 0, silent = FALSE, autostart = TRUE, block_resistance = 1)
id = ++next_id
if(next_id > SHORT_REAL_LIMIT)
next_id = 0
SSexplosions.wave_explosions += src
src.power_initial = power
src.power_falloff_factor = factor
src.power_falloff_constant = constant
src.flash_range = flash
src.fire_probability = fire
src.source = source
src.cycle_speed = speed
src.silent = silent
src.block_resistance = block_resistance
if(!istype(initial))
stack_trace("Wave explosion created without a turf. This better be for debugging purposes.")
return
if(autostart)
start(initial)
/datum/wave_explosion/Destroy()
if(running)
stop(FALSE)
return ..()
/datum/wave_explosion/proc/start(list/turf/_starting)
if(running)
CRASH("Attempted to start() a running wave explosion")
if(!islist(_starting))
_starting = list(_starting)
var/list/mob/to_flash = list()
var/list/feedback = list()
var/list/mob/mob_potential_shake = list()
var/list/mob/closest_to = list()
for(var/i in 1 to _starting.len)
var/turf/starting = _starting[i]
edges[starting] = WEX_ALLDIRS
powers[starting] = power_initial
var/x0 = starting.x
var/y0 = starting.y
var/z0 = starting.z
var/area/areatype = get_area(starting)
feedback += list(list("power" = power_initial, factor = "factor", constant = "constant", flash = "flash", fire = "fire", speed = "speed", "x" = x0, "y" = y0, "z" = z0, "area" = areatype.type, "time" = TIME_STAMP("YYYY-MM-DD hh:mm:ss", 1)))
// 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
if(!silent)
for(var/mob/M in GLOB.player_list)
// Double check for client
var/turf/M_turf = get_turf(M)
if(M_turf && M_turf.z == z0)
var/dist = get_dist(M_turf, starting)
if(isnull(mob_potential_shake[M]))
mob_potential_shake[M] = dist
closest_to[M] = starting
else if(mob_potential_shake[M] < dist)
mob_potential_shake[M] = dist
closest_to[M] = starting
for(var/array in GLOB.doppler_arrays)
var/obj/machinery/doppler_array/A = array
A.sense_wave_explosion(starting, power_initial, cycle_speed)
// Flash mobs
if(flash_range)
for(var/mob/living/L in viewers(flash_range, starting))
to_flash |= L
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/far_dist = sqrt(power_initial) * 7.5
for(var/mob/M in mob_potential_shake)
var/dist = mob_potential_shake[M]
var/baseshakeamount
if(sqrt(power_initial) - dist > 0)
baseshakeamount = sqrt((sqrt(power_initial) - dist)*0.1)
// If inside the blast radius + world.view - 2
if(dist <= round(2 * sqrt(power_initial) + world.view - 2, 1))
M.playsound_local(closest_to[M], null, 100, 1, frequency, max_distance = 5, 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, 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(closest_to[M], null, far_volume, 1, frequency, max_distance = 5, S = far_explosion_sound)
if(baseshakeamount > 0)
shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, 2.5))
for(var/i in 1 to to_flash.len)
var/mob/living/L = to_flash[i]
L.flash_act()
SSblackbox.record_feedback("associative", "wave_explosion", 1, feedback)
if(!cycle)
cycle = 1
SSexplosions.active_wave_explosions += src
running = TRUE
cycle_start = world.time - cycle_speed
tick()
/datum/wave_explosion/proc/stop(delete = TRUE)
SSexplosions.active_wave_explosions -= src
SSexplosions.currentrun -= src
edges = null
powers = null
exploded_last = null
cycle = null
running = FALSE
qdel(src)
#define SHOULD_SUSPEND ((cycle_start + cycle_speed) > world.time)
/**
* Called by SSexplosions to propagate this.
* Return TRUE if postponed
*/
/datum/wave_explosion/proc/tick()
/// Each tick goes through one full cycle.
// This can be changed to a "continuous process" system where indexes are tracked if needed.
if(!src.edges.len)
// we're done
finished = TRUE
stop(TRUE)
return TRUE
if(SHOULD_SUSPEND)
return TRUE
// Set up variables
var/turf/T
var/turf/expanding
var/power
var/returned
var/blocked
var/dir
// insanity define to explode a turf with a certain amount of power, direction, and set returned.
#define WEX_ACT(_T, _P, _D) \
returned = max(0, _T.wave_explode(_P, src, _D)); \
blocked = _P - returned; \
if(!block_resistance) { \
if(blocked > EXPLOSION_POWER_NO_RESIST_THRESHOLD) { \
returned = 0; \
} \
} \
else if(blocked) { \
returned = _P - (blocked / block_resistance); \
}; \
returned = round((returned * power_falloff_factor) - power_falloff_constant, EXPLOSION_POWER_QUANTIZATION_ACCURACY); \
if(prob(fire_probability)) { \
new /obj/effect/hotspot(_T); \
};
// Cache hot lists
var/list/turf/edges = src.edges
var/list/turf/powers = src.powers
var/list/turf/exploded_last = src.exploded_last
// prepare expansions
var/list/turf/edges_next = list()
var/list/turf/powers_next = list()
var/list/turf/powers_returned = list()
var/list/turf/diagonals = list()
var/list/turf/diagonal_powers = list()
var/list/turf/diagonal_powers_max = list()
// to_chat(world, "DEBUG: cycle start edges [english_list_assoc(edges)]")
// Process cardinals:
// Explode all cardinals and expand in directions, gathering all cardinals it should go to.
// Power for when things meet in the middle should be the greatest of the two.
for(var/i in edges)
T = i
power = powers[T]
dir = edges[T]
WEX_ACT(T, power, dir)
if(returned < power_considered_dead)
continue
powers_returned[T] = returned
// diagonal power calc when multiple things hit one diagonal
#define CALCULATE_DIAGONAL_POWER(existing, adding, maximum) min(maximum, existing + adding)
// diagonal hitting cardinal expansion
#define CALCULATE_DIAGONAL_CROSS_POWER(existing, adding) max(existing, adding)
// insanity define to mark the next set of cardinals.
#define CARDINAL_MARK(ndir, cdir, edir) \
if(edir & cdir) { \
expanding = get_step(T,ndir); \
if(expanding && !exploded_last[expanding] && !edges[expanding]) { \
powers_next[expanding] = max(powers_next[expanding], returned); \
edges_next[expanding] = (cdir | edges_next[expanding]); \
}; \
};
// insanity define to do diagonal marking as 2 substeps
#define DIAGONAL_SUBSTEP(ndir, cdir, edir) \
expanding = get_step(T,ndir); \
if(expanding && !exploded_last[expanding] && !edges[expanding]) { \
if(!edges_next[expanding]) { \
diagonal_powers_max[expanding] = max(diagonal_powers_max[expanding], returned, powers[T]); \
diagonal_powers[expanding] = CALCULATE_DIAGONAL_POWER(diagonal_powers[expanding], returned, diagonal_powers_max[expanding]); \
diagonals[expanding] = (cdir | diagonals[expanding]); \
}; \
else { \
powers_next[expanding] = CALCULATE_DIAGONAL_CROSS_POWER(powers_next[expanding], returned); \
}; \
};
// insanity define to mark the diagonals that would otherwise be missed
// this only works because right now, WEX_DIR_X is the same as a byond dir
// and we know we're only passing in one dir at a time.
// if this ever stops being the case, and explosions break when you touch this, now you know why.
#define DIAGONAL_MARK(ndir, cdir, edir) \
if(edir & cdir) { \
DIAGONAL_SUBSTEP(turn(ndir, 90), turn(cdir, 90), edir); \
DIAGONAL_SUBSTEP(turn(ndir, -90), turn(cdir, -90), edir); \
};
CARDINAL_MARK(NORTH, WEX_DIR_NORTH, dir)
CARDINAL_MARK(SOUTH, WEX_DIR_SOUTH, dir)
CARDINAL_MARK(EAST, WEX_DIR_EAST, dir)
CARDINAL_MARK(WEST, WEX_DIR_WEST, dir)
// to_chat(world, "DEBUG: cycle mid edges_next [english_list_assoc(edges_next)]")
// Sweep after cardinals for diagonals
for(var/i in edges)
T = i
power = powers[T]
dir = edges[T]
returned = powers_returned[T]
DIAGONAL_MARK(NORTH, WEX_DIR_NORTH, dir)
DIAGONAL_MARK(SOUTH, WEX_DIR_SOUTH, dir)
DIAGONAL_MARK(EAST, WEX_DIR_EAST, dir)
DIAGONAL_MARK(WEST, WEX_DIR_WEST, dir)
// to_chat(world, "DEBUG: cycle mid diagonals [english_list_assoc(diagonals)]")
// Process diagonals:
for(var/i in diagonals)
T = i
power = diagonal_powers[T]
dir = diagonals[T]
WEX_ACT(T, power, dir)
if(returned < power_considered_dead)
continue
CARDINAL_MARK(NORTH, WEX_DIR_NORTH, dir)
CARDINAL_MARK(SOUTH, WEX_DIR_SOUTH, dir)
CARDINAL_MARK(EAST, WEX_DIR_EAST, dir)
CARDINAL_MARK(WEST, WEX_DIR_WEST, dir)
// to_chat(world, "DEBUG: cycle end edges_next [english_list_assoc(edges_next)]")
// flush lists
src.exploded_last = edges + diagonals
src.edges = edges_next
src.powers = powers_next
cycle++
cycle_start = world.time
#undef SHOULD_SUSPEND
#undef WEX_ACT
#undef CALCULATE_DIAGONAL_POWER
#undef CALCULATE_DIAGONAL_CROSS_POWER
#undef DIAGONAL_SUBSTEP
#undef DIAGONAL_MARK
#undef CARDINAL_MARK