diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 88f9759940..5e36abac15 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -83,6 +83,8 @@
#define COMSIG_ATOM_EXIT "atom_exit" //from base of atom/Exit(): (/atom/movable/exiting, /atom/newloc)
#define COMPONENT_ATOM_BLOCK_EXIT 1
#define COMSIG_ATOM_EXITED "atom_exited" //from base of atom/Exited(): (atom/movable/exiting, atom/newloc)
+/// From base of atom/wave_ex_act(): (datum/wave_explosion/explosion, args)
+#define COMSIG_ATOM_WAVE_EX_ACT "atom_wave_ex_act"
///from base of atom/ex_act(): (severity, target)
#define COMSIG_ATOM_EX_ACT "atom_ex_act"
///from base of atom/emp_act(): (severity)
diff --git a/code/__DEFINES/explosion.dm b/code/__DEFINES/explosion.dm
new file mode 100644
index 0000000000..adba852ff1
--- /dev/null
+++ b/code/__DEFINES/explosion.dm
@@ -0,0 +1,86 @@
+// THIS IS INSANITY
+// These are how wave explosions track when there's not only one direction to keep track of (diagonals, etc)
+#define WEX_DIR_NORTH NORTH
+#define WEX_DIR_SOUTH SOUTH
+#define WEX_DIR_EAST EAST
+#define WEX_DIR_WEST WEST
+#define WEX_ALLDIRS (WEX_DIR_NORTH | WEX_DIR_SOUTH | WEX_DIR_EAST | WEX_DIR_WEST)
+
+/// Default explosion power to consider an explosion over
+#define EXPLOSION_POWER_DEAD 2.5
+/// Default explosion falloff
+#define EXPLOSION_DEFAULT_FALLOFF_MULTIPLY 0.98
+/// Default explosion constant falloff
+#define EXPLOSION_DEFAULT_FALLOFF_SUBTRACT 5
+/// Block amount at which point having 0 block resistance will result in a full block
+#define EXPLOSION_POWER_NO_RESIST_THRESHOLD 5
+
+/// Explosion power quantization
+#define EXPLOSION_POWER_QUANTIZATION_ACCURACY 0.1
+
+// [explosion_flags] variable on /atom
+/// No blocking if we're not dense
+#define EXPLOSION_FLAG_DENSITY_DEPENDENT (1<<0)
+/// If we survive the explosion, we block ALL the power and ignore the results of wave_ex_act().
+#define EXPLOSION_FLAG_HARD_OBSTACLE (1<<1)
+
+// Standardized explosion powers
+/// Maxcap
+#define EXPLOSION_POWER_MAXCAP 500
+/// erases shreds from explosions/item damage
+#define EXPLOSION_POWER_ERASE_SHREDS 400
+/// Gibs most mobs
+#define EXPLOSION_POWER_NORMAL_MOB_GIB 400
+
+// Walls
+#define EXPLOSION_POWER_WALL_SCRAPE 400
+#define EXPLOSION_POWER_WALL_DISMANTLE 300
+#define EXPLOSION_POWER_WALL_MINIMUM_DISMANTLE 200
+
+#define EXPLOSION_POWER_RWALL_SCRAPE 450
+#define EXPLOSION_POWER_RWALL_DISMANTLE 400
+#define EXPLOSION_POWER_RWALL_MINIMUM_DISMANTLE 300
+
+// Floors
+#define EXPLOSION_POWER_FLOOR_TILE_BREAK 50
+#define EXPLOSION_POWER_FLOOR_MINIMUM_TURF_BREAK 125
+#define EXPLOSION_POWER_FLOOR_TURF_BREAK_BONUS 225
+#define EXPLOSION_POWER_FLOOR_TURF_BREAK 350
+#define EXPLOSION_POWER_FLOOR_TURF_SCRAPE 425
+#define EXPLOSION_POWER_FLOOR_SHIELDED_IMMUNITY 250
+
+// Helpers
+/// Explosion power to object damage (without taking into consideration armor)
+#define EXPLOSION_POWER_STANDARD_SCALE_OBJECT_DAMAGE(power, multiplier) (power>500)?(10*(power**0.6)*multiplier):(0.1*(power**1.3)*multiplier)
+/// Explosion power to object damage for hard obstacles
+#define EXPLOSION_POWER_STANDARD_SCALE_HARD_OBSTACLE_DAMAGE(power, multiplier) (power>500)?(10*(power**0.6)*multiplier):(0.15*(power**1.3)*multiplier)
+/// Explosion power to object damage for windows
+#define EXPLOSION_POWER_STANDARD_SCALE_WINDOW_DAMAGE(power, multiplier) (power>500)?(10*(power**0.6)*multiplier):(0.2*(power**1.3)*multiplier)
+/// Default brute damage to do to living things
+#define EXPLOSION_POWER_STANDARD_SCALE_MOB_DAMAGE(power, multiplier) ((power / 2) * multiplier)
+
+// Damage factors
+/// Factor to multiply damage to a door by if it's open (and therefore not blocking the explosion)
+#define EXPLOSION_DAMAGE_OPEN_DOOR_FACTOR 0.25
+
+// Standardized explosion constant blocks
+#define EXPLOSION_BLOCK_WINDOW 10
+#define EXPLOSION_BLOCK_MACHINE 20
+#define EXPLOSION_BLOCK_SPACE 20
+#define EXPLOSION_BLOCK_REINFORCED_WINDOW 50
+#define EXPLOSION_BLOCK_DENSE_FILLER 50
+#define EXPLOSION_BLOCK_WALL 75
+#define EXPLOSION_BLOCK_BLAST_PROOF 250
+#define EXPLOSION_BLOCK_BOROSILICATE_WINDOW 250
+#define EXPLOSION_BLOCK_EXTREME 250
+
+// Standardized explosion factor blocks
+#define EXPLOSION_DAMPEN_MACHINE 0.95
+#define EXPLOSION_DAMPEN_SPACE 0.95
+#define EXPLOSION_DAMPEN_WINDOW 0.95
+#define EXPLOSION_DAMPEN_REINFORCED_WINDOW 0.9
+#define EXPLOSION_DAMPEN_DENSE_FILLER 0.85
+#define EXPLOSION_DAMPEN_WALL 0.8
+#define EXPLOSION_DAMPEN_BOROSILICATE_WINDOW 0.65
+#define EXPLOSION_DAMPEN_BLAST_PROOF 0.65
+#define EXPLOSION_DAMPEN_EXTREME 0.5
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index deb578464f..2c8001b086 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -180,6 +180,7 @@
#define FIRE_PRIORITY_PROJECTILES 200
#define FIRE_PRIORITY_TICKER 200
#define FIRE_PRIORITY_ATMOS_ADJACENCY 300
+#define FIRE_PRIORITY_EXPLOSIONS 350
#define FIRE_PRIORITY_STATPANEL 390
#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_RUNECHAT 410
@@ -219,7 +220,6 @@
A.flags_1 &= ~OVERLAY_QUEUED_1;\
} while(FALSE)
-
/**
Create a new timer and add it to the queue.
* Arguments:
diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm
new file mode 100644
index 0000000000..30a05a7505
--- /dev/null
+++ b/code/controllers/subsystem/explosions.dm
@@ -0,0 +1,23 @@
+SUBSYSTEM_DEF(explosions)
+ name = "Explosions"
+ wait = 1
+ flags = SS_TICKER
+ priority = FIRE_PRIORITY_EXPLOSIONS
+ var/static/list/datum/wave_explosion/wave_explosions = list()
+ var/static/list/datum/wave_explosion/active_wave_explosions = list()
+ var/static/list/datum/wave_explosion/currentrun = list()
+
+/datum/controller/subsystem/explosions/fire(resumed)
+ if(!resumed)
+ currentrun = active_wave_explosions.Copy()
+ var/datum/wave_explosion/E
+ var/ran = 0
+ while(length(currentrun) && !MC_TICK_CHECK)
+ ran = 0
+ for(var/i in currentrun)
+ E = i
+ if(E.tick())
+ currentrun -= E
+ ran++
+ if(!ran)
+ break
diff --git a/code/datums/explosion2.dm b/code/datums/explosion2.dm
new file mode 100644
index 0000000000..75f65e4b23
--- /dev/null
+++ b/code/datums/explosion2.dm
@@ -0,0 +1,378 @@
+/// 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) { \
+ CARDINAL_MARK_NOCHECK(ndir, cdir, edir); \
+ };
+
+#define CARDINAL_MARK_NOCHECK(ndir, cdir, edir) \
+ 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
+#define DIAGONAL_MARK(ndir, cdir, edir) \
+ if(edir & cdir) { \
+ DIAGONAL_MARK_NOCHECK(ndir, cdir, edir); \
+ };
+
+ // 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_NOCHECK(ndir, cdir, edir) \
+ DIAGONAL_SUBSTEP(turn(ndir, 90), turn(cdir, 90), edir); \
+ DIAGONAL_SUBSTEP(turn(ndir, -90), turn(cdir, -90), edir);
+
+ // mark
+#define MARK(ndir, cdir, edir) \
+ if(edir & cdir) { \
+ CARDINAL_MARK_NOCHECK(ndir, cdir, edir); \
+ DIAGONAL_MARK_NOCHECK(ndir, cdir, 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 DIAGONAL_SUBSTEP
+#undef DIAGONAL_MARK
+#undef CARDINAL_MARK
+#undef MARK
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 18674cdb17..3009831da7 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -36,6 +36,14 @@
///Value used to increment ex_act() if reactionary_explosions is on
var/explosion_block = 0
+ /// Flags for explosions
+ var/explosion_flags = NONE
+ /// Amount to decrease wave explosions by
+ var/wave_explosion_block = 0
+ /// Amount to multiply wave explosions by
+ var/wave_explosion_multiply = 1
+
+ //its inherent color, the colored paint applied on it, special color effect etc...
/**
* used to store the different colors on an atom
*
@@ -43,7 +51,6 @@
*/
var/list/atom_colours
-
/// a very temporary list of overlays to remove
var/list/remove_overlays
/// a very temporary list of overlays to add
@@ -556,6 +563,34 @@
contents_explosion(severity, target)
SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target)
+/**
+ * Called when a wave explosion hits this atom. Do not override this.
+ *
+ * Returns explosion power to "allow through".
+ */
+/atom/proc/wave_explode(power, datum/wave_explosion/explosion, dir)
+ set waitfor = FALSE
+ // SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SEND_SIGNAL(src, COMSIG_ATOM_WAVE_EX_ACT, args)
+ . = wave_ex_act(power, explosion, dir) // this must happen first for stuff like destruction/damage to tick.
+ if(isnull(.))
+ stack_trace("wave_ex_act on [type] failed to return a number. defaulting to no blocking.")
+ return power
+ if((explosion_flags & EXPLOSION_FLAG_DENSITY_DEPENDENT) && !density)
+ return power // no block
+ else if((explosion_flags & EXPLOSION_FLAG_HARD_OBSTACLE) && !QDELETED(src))
+ return 0 // fully blocked
+
+/**
+ * Called when a wave explosion hits this atom.
+ *
+ * Returns explosion power to "allow through". Standard handling and flag overrides in [wave_explode()].
+ */
+/atom/proc/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ // SHOULD_NOT_SLEEP(TRUE)
+ return power * wave_explosion_multiply - wave_explosion_block
+
/atom/proc/blob_act(obj/structure/blob/B)
SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
return
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index d26f8c5639..2ccf7d317d 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -96,6 +96,10 @@ Class Procs:
flags_ricochet = RICOCHET_HARD
ricochet_chance_mod = 0.3
+ explosion_flags = EXPLOSION_FLAG_DENSITY_DEPENDENT
+ wave_explosion_block = EXPLOSION_BLOCK_MACHINE
+ wave_explosion_multiply = EXPLOSION_DAMPEN_MACHINE
+
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index f4a65ce984..4eed168ad6 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -54,6 +54,8 @@
assemblytype = /obj/structure/door_assembly
normalspeed = 1
explosion_block = 1
+ wave_explosion_block = EXPLOSION_BLOCK_WALL
+ wave_explosion_multiply = EXPLOSION_DAMPEN_WALL
hud_possible = list(DIAG_AIRLOCK_HUD)
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 63a8ad22d4..a20374f878 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -16,6 +16,11 @@
interaction_flags_atom = INTERACT_ATOM_UI_INTERACT
+ wave_explosion_block = EXPLOSION_BLOCK_DENSE_FILLER
+ wave_explosion_multiply = EXPLOSION_DAMPEN_DENSE_FILLER
+
+ explosion_flags = EXPLOSION_FLAG_HARD_OBSTACLE | EXPLOSION_FLAG_DENSITY_DEPENDENT
+
var/secondsElectrified = 0
var/air_tight = FALSE //TRUE means density will be set as soon as the door begins to close
var/shockedby
@@ -412,3 +417,8 @@
/obj/machinery/door/GetExplosionBlock()
return density ? real_explosion_block : 0
+
+/obj/machinery/door/wave_explosion_damage(power, datum/wave_explosion/explosion)
+ . = ..()
+ if(!density)
+ return . * EXPLOSION_DAMAGE_OPEN_DOOR_FACTOR
diff --git a/code/game/machinery/doors/passworddoor.dm b/code/game/machinery/doors/passworddoor.dm
index 1ec5b3c373..0c64151bca 100644
--- a/code/game/machinery/doors/passworddoor.dm
+++ b/code/game/machinery/doors/passworddoor.dm
@@ -71,3 +71,6 @@
/obj/machinery/door/password/ex_act(severity, target)
return
+
+/obj/machinery/door/password/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return 0 //no.
diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm
index 4226d8a439..8c7fc692bc 100644
--- a/code/game/machinery/doors/poddoor.dm
+++ b/code/game/machinery/doors/poddoor.dm
@@ -8,6 +8,8 @@
closingLayer = CLOSED_BLASTDOOR_LAYER
sub_door = TRUE
explosion_block = 3
+ wave_explosion_block = EXPLOSION_BLOCK_BLAST_PROOF
+ wave_explosion_multiply = EXPLOSION_DAMPEN_BLAST_PROOF
heat_proof = TRUE
safe = FALSE
max_integrity = 600
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index dd885542c9..af7545d3c9 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -360,6 +360,8 @@
max_integrity = 300 //Stronger doors for prison (regular window door health is 200)
reinf = 1
explosion_block = 1
+ wave_explosion_block = EXPLOSION_BLOCK_REINFORCED_WINDOW
+ wave_explosion_multiply = EXPLOSION_DAMPEN_REINFORCED_WINDOW
/obj/machinery/door/window/brigdoor/security/cell
name = "cell door"
diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm
index 1cdb81ee1d..2066e88f85 100644
--- a/code/game/machinery/doppler_array.dm
+++ b/code/game/machinery/doppler_array.dm
@@ -114,6 +114,44 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
LAZYADD(message_log, messages.Join(" "))
return TRUE
+/obj/machinery/doppler_array/proc/sense_wave_explosion(turf/epicenter, power, speed)
+ if(stat & NOPOWER)
+ return FALSE
+ var/turf/zone = get_turf(src)
+ if(zone.z != epicenter.z)
+ return FALSE
+
+ if(next_announce > world.time)
+ return FALSE
+ next_announce = world.time + cooldown
+
+ var/distance = get_dist(epicenter, zone)
+ var/direct = get_dir(zone, epicenter)
+
+ if(distance > max_dist)
+ return FALSE
+ if(!(direct & dir) && !integrated)
+ return FALSE
+
+
+ var/list/messages = list("Explosive shockwave detected.", \
+ "Epicenter at: grid ([epicenter.x],[epicenter.y]). Shockwave expanding at a theoretical speed of [speed] m/s.", \
+ "Wave energy: [power]MJ.")
+
+ if(integrated)
+ var/obj/item/clothing/head/helmet/space/hardsuit/helm = loc
+ if(!helm || !istype(helm, /obj/item/clothing/head/helmet/space/hardsuit))
+ return FALSE
+ helm.display_visor_message("Waveform explosion detected! Wave energy: [power]MJ.")
+ else
+ for(var/message in messages)
+ say(message)
+ if(LAZYLEN(message_log) > list_limit)
+ say("Storage buffer is full! Clearing buffers...")
+ LAZYCLEARLIST(message_log)
+ LAZYADD(message_log, messages.Join(" "))
+ return TRUE
+
/obj/machinery/doppler_array/examine(mob/user)
. = ..()
. += "Its dish is facing to the [dir2text(dir)]."
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index bb566f4d87..fe4ab20617 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -50,6 +50,10 @@
/obj/effect/decal/cleanable/glass/ex_act()
qdel(src)
+/obj/effect/decal/cleanable/glass/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ qdel(src)
+ return power
+
/obj/effect/decal/cleanable/glass/plasma
icon_state = "plasmatiny"
@@ -131,6 +135,9 @@
/obj/effect/decal/cleanable/greenglow/ex_act()
return
+/obj/effect/decal/cleanable/greenglow/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/obj/effect/decal/cleanable/cobweb
name = "cobweb"
desc = "Somebody should remove that."
@@ -253,6 +260,11 @@
if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion.
qdel(src)
+/obj/effect/decal/cleanable/shreds/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ if(power > EXPLOSION_POWER_ERASE_SHREDS)
+ qdel(src)
+ return power // no block
+
/obj/effect/decal/cleanable/shreds/Initialize()
pixel_x = rand(-10, 10)
pixel_y = rand(-10, 10)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 8a09bca0bf..5089d26c88 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -248,6 +248,14 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
loc = null
loc = T
+/obj/item/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ . = ..()
+ if(!anchored)
+ var/throw_dist = round(rand(3, max(3, 2.5 * sqrt(power))), 1)
+ throw_speed = EXPLOSION_THROW_SPEED
+ var/turf/target = get_ranged_target_turf(src, dir, throw_dist)
+ throw_at(target, throw_dist, EXPLOSION_THROW_SPEED)
+
/obj/item/examine(mob/user) //This might be spammy. Remove?
. = ..()
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index e584cad9e8..aa48930d6c 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -649,6 +649,9 @@
pre_noise = TRUE
post_noise = FALSE
+ var/stun_delay = 0 // how long it takes for you to be able to stun someone with the spraycan again
+ var/last_stun_time = 0
+
/obj/item/toy/crayon/spraycan/isValidSurface(surface)
return (istype(surface, /turf/open/floor) || istype(surface, /turf/closed/wall))
@@ -716,7 +719,8 @@
if(C.client)
C.blur_eyes(3)
C.blind_eyes(1)
- if(C.get_eye_protection() <= 0) // no eye protection? ARGH IT BURNS.
+ if(C.get_eye_protection() <= 0 && (last_stun_time + stun_delay) <= world.time) // no eye protection? ARGH IT BURNS.
+ last_stun_time = world.time
C.confused = max(C.confused, 3)
C.DefaultCombatKnockdown(60)
if(ishuman(C) && actually_paints)
@@ -771,6 +775,7 @@
name = "cyborg spraycan"
desc = "A metallic container containing shiny synthesised paint."
charges = -1
+ stun_delay = 5 SECONDS
/obj/item/toy/crayon/spraycan/borg/draw_on(atom/target,mob/user,proximity, params)
var/diff = ..()
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 73a21e4a25..02f2009667 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -71,6 +71,19 @@
if(3)
take_damage(rand(10, 90), BRUTE, "bomb", 0)
+/obj/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ if(resistance_flags & INDESTRUCTIBLE)
+ return power
+ . = ..()
+ if(explosion.source == src)
+ obj_integrity = 0
+ qdel(src)
+ return
+ take_damage(wave_explosion_damage(power, explosion), BRUTE, "bomb", 0)
+
+/obj/proc/wave_explosion_damage(power, datum/wave_explosion/explosion)
+ return (explosion_flags & EXPLOSION_FLAG_HARD_OBSTACLE)? EXPLOSION_POWER_STANDARD_SCALE_HARD_OBSTACLE_DAMAGE(power, explosion.hard_obstacle_mod) : EXPLOSION_POWER_STANDARD_SCALE_OBJECT_DAMAGE(power, explosion.object_damage_mod)
+
/obj/bullet_act(obj/item/projectile/P)
. = ..()
playsound(src, P.hitsound, 50, 1)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 53bcd28fa1..e40e158895 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -43,6 +43,10 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
attack_hand_speed = CLICK_CD_MELEE
attack_hand_is_action = TRUE
+ explosion_flags = EXPLOSION_FLAG_HARD_OBSTACLE
+ wave_explosion_block = EXPLOSION_BLOCK_WINDOW
+ wave_explosion_multiply = EXPLOSION_DAMPEN_WINDOW
+
/// Electrochromatic status
var/electrochromatic_status = NOT_ELECTROCHROMATIC
/// Electrochromatic ID. Set the first character to ! to replace with a SSmapping generated pseudorandom obfuscated ID for mapping purposes.
@@ -111,6 +115,9 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
return TRUE
return FALSE
+/obj/structure/window/wave_explosion_damage(power, datum/wave_explosion/explosion)
+ return EXPLOSION_POWER_STANDARD_SCALE_WINDOW_DAMAGE(power, explosion.window_shatter_mod)
+
/obj/structure/window/narsie_act()
add_atom_colour(NARSIE_WINDOW_COLOUR, FIXED_COLOUR_PRIORITY)
@@ -520,6 +527,8 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100)
max_integrity = 50
explosion_block = 1
+ wave_explosion_block = EXPLOSION_BLOCK_REINFORCED_WINDOW
+ wave_explosion_multiply = EXPLOSION_DAMPEN_REINFORCED_WINDOW
glass_type = /obj/item/stack/sheet/rglass
rad_insulation = RAD_HEAVY_INSULATION
ricochet_chance_mod = 0.8
@@ -545,6 +554,8 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100)
max_integrity = 150
explosion_block = 1
+ wave_explosion_block = EXPLOSION_BLOCK_BOROSILICATE_WINDOW
+ wave_explosion_multiply = EXPLOSION_DAMPEN_BOROSILICATE_WINDOW
glass_type = /obj/item/stack/sheet/plasmaglass
cleanable_type = /obj/effect/decal/cleanable/glass/plasma
rad_insulation = RAD_NO_INSULATION
@@ -570,6 +581,8 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100)
max_integrity = 500
explosion_block = 2
+ wave_explosion_block = EXPLOSION_BLOCK_EXTREME
+ wave_explosion_multiply = EXPLOSION_BLOCK_EXTREME
glass_type = /obj/item/stack/sheet/plasmarglass
/obj/structure/window/plasma/reinforced/spawner/east
@@ -742,6 +755,8 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup)
max_integrity = 80
armor = list("melee" = 60, "bullet" = 25, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100)
explosion_block = 2 //fancy AND hard to destroy. the most useful combination.
+ wave_explosion_block = EXPLOSION_BLOCK_BOROSILICATE_WINDOW
+ wave_explosion_multiply = EXPLOSION_DAMPEN_BOROSILICATE_WINDOW
decon_speed = 40
glass_type = /obj/item/stack/tile/brass
glass_amount = 1
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm
index be0c444541..91a8ba4f46 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed.dm
@@ -6,6 +6,8 @@
blocks_air = 1
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
rad_insulation = RAD_MEDIUM_INSULATION
+ wave_explosion_block = 10
+ wave_explosion_multiply = 0.75
/turf/closed/Initialize()
. = ..()
@@ -28,6 +30,7 @@
name = "wall"
icon = 'icons/turf/walls.dmi'
explosion_block = 50
+ wave_explosion_block = INFINITY
/turf/closed/indestructible/rust_heretic_act()
return
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index 565d5640e3..e5034e4b2f 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -12,6 +12,19 @@
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ /// Minimum explosion power to break tile
+ var/explosion_power_break_tile = EXPLOSION_POWER_FLOOR_TILE_BREAK
+ /// Minimum explosion power to break turf
+ var/explosion_power_break_turf = EXPLOSION_POWER_FLOOR_TURF_BREAK
+ //// Minimum explosion power to scrape away the floor
+ var/explosion_power_turf_scrape = EXPLOSION_POWER_FLOOR_TURF_SCRAPE
+ //// Shielded turfs are completely protected from anything under this
+ var/explosion_power_protect_shielded = EXPLOSION_POWER_FLOOR_SHIELDED_IMMUNITY
+ /// Starting from here, there's a chance for this to break
+ var/explosion_power_minimum_chance_break = EXPLOSION_POWER_FLOOR_MINIMUM_TURF_BREAK
+ /// Starting from here, +20% chance to break turf.
+ var/explosion_power_break_turf_bonus = EXPLOSION_POWER_FLOOR_TURF_BREAK_BONUS
+
var/icon_regular_floor = "floor" //used to remember what icon the tile should have by default
var/icon_plating = "plating"
thermal_conductivity = 0.004
@@ -98,6 +111,48 @@
src.break_tile()
src.hotspot_expose(1000,CELL_VOLUME)
+/turf/open/floor/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ var/shielded = is_shielded()
+ . = ..()
+ if(shielded)
+ if(power < explosion_power_protect_shielded)
+ return
+ else
+ power -= explosion_power_protect_shielded
+ hotspot_expose(1000, CELL_VOLUME)
+ if(power < explosion_power_break_tile)
+ return
+ if(power < explosion_power_minimum_chance_break)
+ if(prob(33 + ((explosion_power_break_turf - power) / (explosion_power_break_turf - explosion_power_break_tile))))
+ break_tile()
+ return
+ if((power < explosion_power_turf_scrape) && ((power >= explosion_power_break_turf) || prob((1 - ((explosion_power_break_turf - power) / (explosion_power_break_turf - explosion_power_minimum_chance_break))) * 100 + ((power > explosion_power_break_turf_bonus)? 20 : 0))))
+ switch(pick(1, 2;75, 3))
+ if(1)
+ if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor))
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
+ ReplaceWithLattice()
+ else
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
+ if(prob(33))
+ new /obj/item/stack/sheet/metal(src)
+ return
+ if(2)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
+ return
+ if(3)
+ if(prob(80))
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
+ return
+ else
+ break_tile()
+ hotspot_expose(1000,CELL_VOLUME)
+ if(prob(33))
+ new /obj/item/stack/sheet/metal(src)
+ if(power >= explosion_power_turf_scrape)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
+ return
+
/turf/open/floor/is_shielded()
for(var/obj/structure/A in contents)
if(A.level == 3)
diff --git a/code/game/turfs/simulated/wall/reinf_walls.dm b/code/game/turfs/simulated/wall/reinf_walls.dm
index 03d5cecc26..620c9adfc8 100644
--- a/code/game/turfs/simulated/wall/reinf_walls.dm
+++ b/code/game/turfs/simulated/wall/reinf_walls.dm
@@ -12,6 +12,11 @@
sheet_amount = 1
girder_type = /obj/structure/girder/reinforced
explosion_block = 2
+ wave_explosion_block = EXPLOSION_BLOCK_BLAST_PROOF
+ wave_explosion_multiply = EXPLOSION_DAMPEN_BLAST_PROOF
+ explosion_power_to_scrape = EXPLOSION_POWER_RWALL_SCRAPE
+ explosion_power_to_dismantle = EXPLOSION_POWER_RWALL_DISMANTLE
+ explosion_power_minimum_chance_dismantle = EXPLOSION_POWER_RWALL_MINIMUM_DISMANTLE
rad_insulation = RAD_HEAVY_INSULATION
/turf/closed/wall/r_wall/deconstruction_hints(mob/user)
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index 79ca5add10..76dc6533b6 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -6,6 +6,8 @@
icon = 'icons/turf/walls/wall.dmi'
icon_state = "wall"
explosion_block = 1
+ wave_explosion_block = EXPLOSION_BLOCK_WALL
+ wave_explosion_multiply = EXPLOSION_DAMPEN_WALL
flags_1 = DEFAULT_RICOCHET_1
flags_ricochet = RICOCHET_HARD
thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
@@ -15,6 +17,14 @@
baseturfs = /turf/open/floor/plating
+ explosion_flags = EXPLOSION_FLAG_HARD_OBSTACLE
+ /// Explosion power to disintegrate the wall
+ var/explosion_power_to_scrape = EXPLOSION_POWER_WALL_SCRAPE
+ /// Explosion power to dismantle the wall
+ var/explosion_power_to_dismantle = EXPLOSION_POWER_WALL_DISMANTLE
+ /// Explosion power to potentially dismantle the wall
+ var/explosion_power_minimum_chance_dismantle = EXPLOSION_POWER_WALL_MINIMUM_DISMANTLE
+
var/hardness = 40 //lower numbers are harder. Used to determine the probability of a hulk smashing through.
var/slicing_duration = 100 //default time taken to slice the wall
var/sheet_type = /obj/item/stack/sheet/metal
@@ -91,6 +101,13 @@
if(!density)
..()
+/turf/closed/wall/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ . = ..()
+ var/resultant_power = power * explosion.wall_destroy_mod
+ if(resultant_power >= explosion_power_to_scrape)
+ ScrapeAway()
+ else if((resultant_power >= explosion_power_to_dismantle) || ((resultant_power >= explosion_power_minimum_chance_dismantle) && prob(((resultant_power - explosion_power_minimum_chance_dismantle) / (explosion_power_to_dismantle - explosion_power_minimum_chance_dismantle)) * 100)))
+ dismantle_wall(prob((resultant_power - explosion_power_to_dismantle)/(explosion_power_to_scrape - explosion_power_to_dismantle)), TRUE)
/turf/closed/wall/blob_act(obj/structure/blob/B)
if(prob(50))
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index 0905fb2e9c..b1a4efc97e 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -8,6 +8,8 @@
temperature = TCMB
thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT
heat_capacity = 700000
+ wave_explosion_multiply = EXPLOSION_DAMPEN_SPACE
+ wave_explosion_block = EXPLOSION_BLOCK_SPACE
var/destination_z
var/destination_x
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 0808408192..93735f1079 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -462,6 +462,24 @@ GLOBAL_LIST_EMPTY(station_turfs)
A.ex_act(severity, target)
CHECK_TICK
+/turf/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ . = ..()
+ var/affecting_level
+ if(is_shielded())
+ affecting_level = 3
+ else if(intact)
+ affecting_level = 2
+ else
+ affecting_level = 1
+ var/atom/A
+ for(var/i in contents)
+ if(. <= 0)
+ return 0
+ A = i
+ if(!QDELETED(A) && A.level >= affecting_level)
+ . = A.wave_explode(., explosion, dir)
+ maptext = "[.]"
+
/turf/narsie_act(force, ignore_mobs, probability = 20)
. = (prob(probability) || force)
for(var/I in src)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 96eb4e4a0f..9e784e72cb 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -96,6 +96,7 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/cmd_select_equipment,
/client/proc/cmd_admin_gib_self,
/client/proc/drop_bomb,
+ /client/proc/drop_wave_explosion,
/client/proc/set_dynex_scale,
/client/proc/drop_dynex_bomb,
/client/proc/cinematic,
@@ -550,6 +551,51 @@ GLOBAL_PROTECT(admin_verbs_hideable)
log_admin("[key_name(usr)] created an admin explosion at [epicenter.loc].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Bomb") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/client/proc/drop_wave_explosion()
+ set category = "Special Verbs"
+ set name = "Drop Wave Explosion"
+ set desc = "Cause an explosive shockwave at your location."
+
+ var/power = input(src, "Wave initial power", "Power", 50) as num|null
+ if(isnull(power))
+ return
+ var/falloff = input(src, "Wave innate falloff factor", "Falloff", EXPLOSION_DEFAULT_FALLOFF_MULTIPLY) as num|null
+ if(isnull(falloff))
+ return
+ falloff = max(0, falloff)
+ if(falloff > 1)
+ to_chat(src, "Aborting: Falloff cannot be higher tahn 1.")
+ return
+ var/constant = input(src, "Wave innate falloff constant", "Constant", EXPLOSION_DEFAULT_FALLOFF_SUBTRACT) as num|null
+ if(isnull(constant))
+ return
+ if(constant < 0)
+ to_chat(src, "Aborting: Falloff constant cannot be less than 0.")
+ return
+ var/fire = input(src, "Probability per tile of fire?", "Fire Probability", 0) as num|null
+ if(isnull(fire))
+ return
+ var/speed = input(src, "Speed in ticks to wait between cycles? 0 for fast as possible", "Wait", 0) as num|null
+ if(isnull(speed))
+ return
+ var/block_resistance = input(src, "DANGEROUS: Block resistance? USE 1 IF YOU DO NOT KNOW WHAT YOU ARE DOING.", "Block Negation", 1) as num|null
+ if(isnull(block_resistance))
+ return
+ block_resistance = max(0, block_resistance)
+ if(power > 500)
+ var/sure = alert(src, "Explosion power is extremely high. Are you absolutely sure?", "Uhh...", "No", "Yes")
+ if(sure != "Yes")
+ return
+ // point of no return
+ var/turf/target = get_turf(mob)
+ if(!target)
+ to_chat(src, "Cannot proceed. Not on turf.")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(usr)] creating an admin explosion at [target.loc].")
+ log_admin("[key_name(usr)] created an admin explosion at [target.loc].")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Wave Explosion") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ wave_explosion(target, power, falloff, constant, null, fire, speed = speed, block_resistance = block_resistance)
+
/client/proc/drop_dynex_bomb()
set category = "Admin.Fun"
set name = "Drop DynEx Bomb"
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index 7d669e5c41..eb98635e65 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -633,7 +633,7 @@ This is here to make the tiles around the station mininuke change when it's arme
AddComponent(/datum/component/stationloving, !fake)
/obj/item/disk/nuclear/process()
- ++process_tick
+ process_tick++
if(fake)
STOP_PROCESSING(SSobj, src)
CRASH("A fake nuke disk tried to call process(). Who the fuck and how the fuck")
@@ -650,7 +650,7 @@ This is here to make the tiles around the station mininuke change when it's arme
disk_comfort_level++
if(disk_comfort_level >= 2) //Sleep tight, disky.
- if(process_tick % 30)
+ if(!(process_tick % 30))
visible_message("[src] sleeps soundly. Sleep tight, disky.")
if(last_disk_move < world.time - 5000 && prob((world.time - 5000 - last_disk_move)*0.0001))
var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index 7e4c5c5343..31989fe241 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -177,6 +177,9 @@
/mob/living/simple_animal/revenant/ex_act(severity, target)
return 1 //Immune to the effects of explosions.
+/mob/living/simple_animal/revenant/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/mob/living/simple_animal/revenant/blob_act(obj/structure/blob/B)
return //blah blah blobs aren't in tune with the spirit world, or something.
diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm
index 36ceabe3b4..551c1c5536 100644
--- a/code/modules/awaymissions/gateway.dm
+++ b/code/modules/awaymissions/gateway.dm
@@ -172,13 +172,15 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
/// bumper object, the thing that starts actual teleport
var/obj/effect/gateway_portal_bumper/portal
/// Visual object for handling the viscontents
- var/obj/effect/gateway_portal_effect/portal_visuals
+ /// DISABLED DUE TO BYOND BUG CAUSING STACK OVERFLOWS OF ANY HUMAN INSTANTIATION NEAR AN ACTIVATED GATEWAY.
+ /// Probably due to it referencing each other through the gateway (there's a deep loop, maybe BYOND isn't catching something when it usually would)
+ // var/obj/effect/gateway_portal_effect/portal_visuals
/obj/machinery/gateway/Initialize()
generate_destination()
update_icon()
- portal_visuals = new
- vis_contents += portal_visuals
+ // portal_visuals = new
+ // vis_contents += portal_visuals
return ..()
/obj/machinery/gateway/proc/generate_destination()
@@ -195,7 +197,7 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
if(use_power == ACTIVE_POWER_USE)
use_power = IDLE_POWER_USE
update_icon()
- portal_visuals.reset_visuals()
+ // portal_visuals.reset_visuals()
/obj/machinery/gateway/process()
if((stat & (NOPOWER)) && use_power)
@@ -215,7 +217,7 @@ GLOBAL_LIST_EMPTY(gateway_destinations)
return
target = D
target.activate(destination)
- portal_visuals.setup_visuals(target)
+ // portal_visuals.setup_visuals(target)
generate_bumper()
use_power = ACTIVE_POWER_USE
update_icon()
diff --git a/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
index 972f9a8cc3..943f421a64 100644
--- a/code/modules/food_and_drinks/recipes/drinks_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
@@ -604,6 +604,14 @@
mix_message = "You hear faint sounds of gears turning as it mixes."
mix_sound = 'sound/machines/clockcult/steam_whoosh.ogg'
+/datum/chemical_reaction/pinotmort
+ name = "Pinot Mort"
+ id = /datum/reagent/consumable/ethanol/pinotmort
+ results = list(/datum/reagent/consumable/ethanol/pinotmort = 4)
+ required_reagents = list(/datum/reagent/ash = 2, /datum/reagent/consumable/ethanol/lizardwine = 1, /datum/reagent/consumable/vitfro = 1)
+ mix_message = "You hear an undescribable scream as it mixes... You're not sure how to feel about this."
+ mix_sound = 'sound/effects/tendril_destroyed.ogg'
+
/datum/chemical_reaction/quadruplesec
name = "Quadruple Sec"
id = /datum/reagent/consumable/ethanol/quadruple_sec
diff --git a/code/modules/holiday/halloween/halloween.dm b/code/modules/holiday/halloween/halloween.dm
index d44b94aaf3..f468022918 100644
--- a/code/modules/holiday/halloween/halloween.dm
+++ b/code/modules/holiday/halloween/halloween.dm
@@ -206,6 +206,9 @@
/mob/living/simple_animal/hostile/retaliate/clown/insane/ex_act()
return
+/mob/living/simple_animal/hostile/retaliate/clown/insane/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/mob/living/simple_animal/hostile/retaliate/clown/insane/Life()
timer--
if(target)
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
index e09a0386ee..8fb735e10a 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
@@ -67,6 +67,13 @@
matrixed_sections = MATRIX_RED_GREEN
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+/datum/sprite_accessory/ears/bunnyalt
+ name = "Bunny (Vegas)"
+ icon_state = "bunnyalt"
+ color_src = MATRIXED
+ matrixed_sections = MATRIX_RED_GREEN
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
/datum/sprite_accessory/ears/cat
name = "Cat"
icon_state = "cat"
@@ -258,6 +265,11 @@
icon_state = "bunny"
matrixed_sections = MATRIX_RED_GREEN
+/datum/sprite_accessory/ears/mam_ears/bunnyalt
+ name = "Bunny (Vegas)"
+ icon_state = "bunnyalt"
+ matrixed_sections = MATRIX_RED_GREEN
+
/datum/sprite_accessory/ears/mam_ears/cat
name = "Cat"
icon_state = "cat"
diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm
index 24d456bf8f..b66158db8a 100644
--- a/code/modules/mob/living/bloodcrawl.dm
+++ b/code/modules/mob/living/bloodcrawl.dm
@@ -13,6 +13,10 @@
/obj/effect/dummy/phased_mob/slaughter/ex_act()
return
+
+/obj/effect/dummy/phased_mob/slaughter/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/obj/effect/dummy/phased_mob/slaughter/bullet_act()
return BULLET_ACT_FORCE_PIERCE
diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm
index be03827695..8ffcce248b 100644
--- a/code/modules/mob/living/brain/brain.dm
+++ b/code/modules/mob/living/brain/brain.dm
@@ -46,6 +46,9 @@
/mob/living/brain/ex_act() //you cant blow up brainmobs because it makes transfer_to() freak out when borgs blow up.
return
+/mob/living/brain/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/mob/living/brain/blob_act(obj/structure/blob/B)
return
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 6f5109c90b..c1110af267 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -370,7 +370,7 @@
breakouttime = 50
visible_message("[src] is trying to break [I]!")
to_chat(src, "You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)")
- if(do_after(src, breakouttime, 0, target = src))
+ if(do_after(src, breakouttime, 0, target = src, required_mobility_flags = MOBILITY_RESIST))
clear_cuffs(I, cuff_break)
else
to_chat(src, "You fail to break [I]!")
diff --git a/code/modules/mob/living/carbon/human/species_types/anthromorph.dm b/code/modules/mob/living/carbon/human/species_types/anthropomorph.dm
similarity index 97%
rename from code/modules/mob/living/carbon/human/species_types/anthromorph.dm
rename to code/modules/mob/living/carbon/human/species_types/anthropomorph.dm
index d718db818c..328a557931 100644
--- a/code/modules/mob/living/carbon/human/species_types/anthromorph.dm
+++ b/code/modules/mob/living/carbon/human/species_types/anthropomorph.dm
@@ -1,5 +1,5 @@
/datum/species/mammal
- name = "Anthromorph"
+ name = "Anthropomorph"
id = SPECIES_MAMMAL
default_color = "4B4B4B"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAIR,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
@@ -20,7 +20,7 @@
allowed_limb_ids = list("mammal","aquatic","avian")
/datum/species/mammal/synthetic
- name = "Synthetic Anthromorph"
+ name = "Synthetic Anthropomorph"
id = SPECIES_MAMMAL_SYNTHETIC
species_traits = list(MUTCOLORS,NOTRANSSTING,EYECOLOR,LIPS,HAIR,ROBOTIC_LIMBS,HAS_FLESH,HAS_BONE,WINGCOLOR,HORNCOLOR)
diff --git a/code/modules/mob/living/carbon/human/species_types/bugmen.dm b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
index 773651d464..c649339fe3 100644
--- a/code/modules/mob/living/carbon/human/species_types/bugmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
@@ -1,5 +1,5 @@
/datum/species/insect
- name = "Anthromorphic Insect"
+ name = "Anthropomorphic Insect"
id = SPECIES_INSECT
say_mod = "chitters"
default_color = "00FF00"
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index c3e406244b..5a185cbeb8 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -1,5 +1,5 @@
/datum/species/fly
- name = "Anthromorphic Fly"
+ name = "Anthropomorphic Fly"
id = SPECIES_FLY
say_mod = "buzzes"
species_traits = list(NOEYES,HAS_FLESH,HAS_BONE)
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 40645b13d4..cfbb8c31c0 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -1,6 +1,6 @@
/datum/species/lizard
// Reptilian humanoids with scaled skin and tails.
- name = "Anthromorphic Lizard"
+ name = "Anthropomorphic Lizard"
id = SPECIES_LIZARD
say_mod = "hisses"
default_color = "00FF00"
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index dcbdf91485..0655b28197 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -1,5 +1,5 @@
/datum/species/mush //mush mush codecuck
- name = "Anthromorphic Mushroom"
+ name = "Anthropomorphic Mushroom"
id = SPECIES_MUSHROOM
mutant_bodyparts = list("caps" = "Round")
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 697c3db35e..0fcafaa208 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -1,6 +1,6 @@
/datum/species/pod
// A mutation caused by a human being ressurected in a revival pod. These regain health in light, and begin to wither in darkness.
- name = "Anthromorphic Plant"
+ name = "Anthropomorphic Plant"
id = SPECIES_POD
default_color = "59CE00"
species_traits = list(MUTCOLORS,EYECOLOR,CAN_SCAR,HAS_FLESH,HAS_BONE)
@@ -71,7 +71,7 @@
H.emote("spin")
/datum/species/pod/pseudo_weak
- name = "Anthromorphic Plant"
+ name = "Anthropomorphic Plant"
id = SPECIES_POD_WEAK
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS,CAN_SCAR,HAS_FLESH,HAS_BONE)
mutant_bodyparts = list("mcolor" = "FFFFFF","mcolor2" = "FFFFFF","mcolor3" = "FFFFFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky", "mam_body_markings" = "Husky", "taur" = "None", "legs" = "Normal Legs")
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 5f3f530437..4c6ede3904 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -438,6 +438,12 @@
return
..()
+/mob/living/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ if(power > EXPLOSION_POWER_NORMAL_MOB_GIB)
+ gib()
+ adjustBruteLoss(EXPLOSION_POWER_STANDARD_SCALE_MOB_DAMAGE(power, explosion.mob_damage_mod))
+ return power
+
//Looking for irradiate()? It's been moved to radiation.dm under the rad_act() for mobs.
/mob/living/acid_act(acidpwr, acid_volume)
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 012515d0e9..7c7ff3779c 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -280,6 +280,9 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
if(3)
adjustBruteLoss(30)
+/mob/living/simple_animal/hostile/guardian/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ adjustBruteLoss(EXPLOSION_POWER_STANDARD_SCALE_MOB_DAMAGE(power, explosion.mob_damage_mod * 0.33))
+
/mob/living/simple_animal/hostile/guardian/gib()
if(summoner)
to_chat(summoner, "Your [src] was blown up!")
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
index e2d6602a88..3940388625 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
@@ -149,6 +149,9 @@
if(EXPLODE_LIGHT)
adjustBruteLoss(50)
+/mob/living/simple_animal/hostile/megafauna/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ adjustBruteLoss(EXPLOSION_POWER_STANDARD_SCALE_MOB_DAMAGE(power, explosion.mob_damage_mod) / 2)
+
/// Sets the next time the megafauna can use a melee or ranged attack, in deciseconds
/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time, ranged_buffer_time)
recovery_time = world.time + buffer_time
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
index cee7e2979d..bda898ef17 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
@@ -59,6 +59,13 @@
if(3)
adjustBruteLoss(110)
+/mob/living/simple_animal/hostile/asteroid/basilisk/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ . = ..()
+ if(power > EXPLOSION_POWER_NORMAL_MOB_GIB)
+ gib()
+ else
+ adjustBruteLoss(EXPLOSION_POWER_STANDARD_SCALE_MOB_DAMAGE(power, explosion.mob_damage_mod))
+
//Watcher
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher
name = "watcher"
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index aae21464b1..f53ac7ac40 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -1490,6 +1490,25 @@ All effects don't start immediately, but rather get worse over time; the rate is
M.stuttering = min(M.stuttering + 3, 3)
..()
+/datum/reagent/consumable/ethanol/pinotmort
+ name = "Pinot Mort"
+ description = "If you just can't get enough of lavaland."
+ color = rgb(167, 36, 36)
+ boozepwr = 20
+ quality = DRINK_FANTASTIC
+ taste_description = "death, ash and lizards"
+ glass_icon_state = "pinotmort"
+ glass_name = "Pinot Mort"
+ glass_desc = "The taste of Lavaland served in a legion skull. You feel like you might regret drinking this."
+ value = REAGENT_VALUE_UNCOMMON
+
+/datum/reagent/consumable/ethanol/pinotmort/on_mob_life(mob/living/carbon/M)
+ if((islizard(M) && M.mind.assigned_role == "Ash Walker") || ispodperson(M) && M.mind.assigned_role == "Lifebringer" || isgolem(M))
+ M.heal_bodypart_damage(1, 1)
+ M.adjustBruteLoss(-2,0)
+ . = 1
+ return ..()
+
/datum/reagent/consumable/ethanol/triple_sec
name = "Triple Sec"
description = "A sweet and vibrant orange liqueur."
diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm
index 9d91b6534d..7a05583071 100644
--- a/code/modules/spells/spell_types/ethereal_jaunt.dm
+++ b/code/modules/spells/spell_types/ethereal_jaunt.dm
@@ -108,5 +108,8 @@
/obj/effect/dummy/phased_mob/spell_jaunt/ex_act(blah)
return
+/obj/effect/dummy/phased_mob/spell_jaunt/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/obj/effect/dummy/phased_mob/spell_jaunt/bullet_act(blah)
return BULLET_ACT_FORCE_PIERCE
diff --git a/code/modules/spells/spell_types/shadow_walk.dm b/code/modules/spells/spell_types/shadow_walk.dm
index 8436de2119..821bf73425 100644
--- a/code/modules/spells/spell_types/shadow_walk.dm
+++ b/code/modules/spells/spell_types/shadow_walk.dm
@@ -90,6 +90,9 @@
/obj/effect/dummy/phased_mob/shadow/ex_act()
return
+/obj/effect/dummy/phased_mob/shadow/wave_ex_act(power, datum/wave_explosion/explosion, dir)
+ return power
+
/obj/effect/dummy/phased_mob/shadow/bullet_act()
return BULLET_ACT_FORCE_PIERCE
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index 0e6456e8b0..12e282200c 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -751,7 +751,6 @@
//phase 2
var/static/regex/awoo_words = regex("howl|awoo|bark")
var/static/regex/nya_words = regex("nya|meow|mewl")
- var/static/regex/sleep_words = regex("sleep|slumber|rest")
var/static/regex/strip_words = regex("strip|derobe|nude|at ease|suit off")
var/static/regex/walk_words = regex("slow down|walk")
var/static/regex/run_words = regex("run|speed up")
@@ -1096,17 +1095,6 @@
H.emote("me", EMOTE_VISIBLE, "lets out a nya!")
E.cooldown += 1
- //SLEEP
- else if((findtext(message, sleep_words)))
- for(var/mob/living/carbon/C in listeners)
- var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
- switch(E.phase)
- if(2 to INFINITY)
- C.Sleeping(45 * power_multiplier)
- E.cooldown += 10
- addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "Drowsiness suddenly overwhelms you as you fall asleep!"), 5)
- to_chat(user, "You send [C] to sleep.")
-
//STRIP
else if((findtext(message, strip_words)))
for(var/V in listeners)
diff --git a/code/modules/uplink/uplink_items/uplink_stealth.dm b/code/modules/uplink/uplink_items/uplink_stealth.dm
index 63d70b9141..f401514542 100644
--- a/code/modules/uplink/uplink_items/uplink_stealth.dm
+++ b/code/modules/uplink/uplink_items/uplink_stealth.dm
@@ -9,7 +9,7 @@
/datum/uplink_item/stealthy_weapons/telescopicbat
name = "Telescopic Baseball Bat"
- desc = "A robust telescopic baseball bat that hits like a truck and can be consealed when collapsed."
+ desc = "A robust telescopic baseball bat that hits like a truck and can be concealed when collapsed."
item = /obj/item/melee/baseball_bat/telescopic
cost = 2
diff --git a/html/changelog.html b/html/changelog.html
index 61681621e7..7e3f24bf3b 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,46 @@
-->
+
14 May 2021
+
keronshb updated:
+
+ - Removes VOG sleep command since it was an undocumented readd.
+
+
zeroisthebiggay updated:
+
+
+
13 May 2021
+
Linzolle updated:
+
+ - anthromorphic -> anthropomorphic
+
+
WanderingFox95 updated:
+
+ - Pinot Mort (Necropolis Wine), a new, (totally healthy) mixed drink!
+
+
qweq12yt updated:
+
+ - Fixed sleeping disky spam (it still sleeps soundly, but every minute instead of every two seconds)
+ - Fixed Hulks not breaking cuffs, zipties, restraints.
+
+
silicons updated:
+
+ - A deterministic wave explosion system has been added. Use it with wave_explosion().
+
+
zeroisthebiggay updated:
+
+ - vegas style bunny ears
+
+
+
12 May 2021
+
DeltaFire15 updated:
+
+ - find_safe_turf no longer always fails on safe oxygen levels(??)
+ - Heretic bladeshatters now actually take the heretic's z into account as intended, instead of always being station z tweak: Message for failing the bladeshatter despite succeeding the do_after tweak: Improves bladeshatter a bit by making it safer codewise
+
+
11 May 2021
LetterN updated:
@@ -755,22 +795,6 @@
- Fixes cosmetic augments missing their foot sprites.
-
- 12 March 2021
- R3dtail updated:
-
- - Adds Periods and moves some words around.
-
-
- 10 March 2021
- Hatterhat updated:
-
- - The femur breaker now actually breaks legs by applying a compound fracture.
-
- Putnam3145 updated:
-
- - uncapped TEG power, buffing high-temp TEGs
-
GoonStation 13 Development Team
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index ae29db429c..ca2426d1f0 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -29276,3 +29276,28 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
bunny232:
- rscadd: Delta station xenobiology department has received enhanced scrubbing and
ventilation capabilities similar to box and meta
+2021-05-12:
+ DeltaFire15:
+ - bugfix: find_safe_turf no longer always fails on safe oxygen levels(??)
+ - bugfix: 'Heretic bladeshatters now actually take the heretic''s z into account
+ as intended, instead of always being station z tweak: Message for failing the
+ bladeshatter despite succeeding the do_after tweak: Improves bladeshatter a
+ bit by making it safer codewise'
+2021-05-13:
+ Linzolle:
+ - spellcheck: anthromorphic -> anthropomorphic
+ WanderingFox95:
+ - rscadd: Pinot Mort (Necropolis Wine), a new, (totally healthy) mixed drink!
+ qweq12yt:
+ - bugfix: Fixed sleeping disky spam (it still sleeps soundly, but every minute instead
+ of every two seconds)
+ - bugfix: Fixed Hulks not breaking cuffs, zipties, restraints.
+ silicons:
+ - code_imp: A deterministic wave explosion system has been added. Use it with wave_explosion().
+ zeroisthebiggay:
+ - rscadd: vegas style bunny ears
+2021-05-14:
+ keronshb:
+ - balance: Removes VOG sleep command since it was an undocumented readd.
+ zeroisthebiggay:
+ - spellcheck: consealed
diff --git a/html/changelogs/AutoChangeLog-pr-14627.yml b/html/changelogs/AutoChangeLog-pr-14627.yml
new file mode 100644
index 0000000000..cde6b62174
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-14627.yml
@@ -0,0 +1,4 @@
+author: "timothyteakettle"
+delete-after: True
+changes:
+ - balance: "borg spraycans have a five second delay before being able to knock someone down again"
diff --git a/html/changelogs/AutoChangeLog-pr-14706.yml b/html/changelogs/AutoChangeLog-pr-14706.yml
deleted file mode 100644
index ecea6647ec..0000000000
--- a/html/changelogs/AutoChangeLog-pr-14706.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-author: "DeltaFire15"
-delete-after: True
-changes:
- - bugfix: "find_safe_turf no longer always fails on safe oxygen levels(??)"
- - bugfix: "Heretic bladeshatters now actually take the heretic's z into account as intended, instead of always being station z
-tweak: Message for failing the bladeshatter despite succeeding the do_after
-tweak: Improves bladeshatter a bit by making it safer codewise"
diff --git a/icons/obj/drinks.dmi b/icons/obj/drinks.dmi
index 1b28b910c1..1c3b10b9de 100644
Binary files a/icons/obj/drinks.dmi and b/icons/obj/drinks.dmi differ
diff --git a/modular_citadel/icons/mob/mam_ears.dmi b/modular_citadel/icons/mob/mam_ears.dmi
index a3f09819f2..1ed3d8fbbe 100644
Binary files a/modular_citadel/icons/mob/mam_ears.dmi and b/modular_citadel/icons/mob/mam_ears.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 017bd393e7..2e5bd87e51 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -51,6 +51,7 @@
#include "code\__DEFINES\economy.dm"
#include "code\__DEFINES\events.dm"
#include "code\__DEFINES\exosuit_fabs.dm"
+#include "code\__DEFINES\explosion.dm"
#include "code\__DEFINES\exports.dm"
#include "code\__DEFINES\fantasy_affixes.dm"
#include "code\__DEFINES\food.dm"
@@ -320,6 +321,7 @@
#include "code\controllers\subsystem\disease.dm"
#include "code\controllers\subsystem\economy.dm"
#include "code\controllers\subsystem\events.dm"
+#include "code\controllers\subsystem\explosions.dm"
#include "code\controllers\subsystem\fail2topic.dm"
#include "code\controllers\subsystem\fire_burning.dm"
#include "code\controllers\subsystem\fluid.dm"
@@ -410,6 +412,7 @@
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\explosion.dm"
+#include "code\datums\explosion2.dm"
#include "code\datums\forced_movement.dm"
#include "code\datums\holocall.dm"
#include "code\datums\http.dm"
@@ -2625,7 +2628,7 @@
#include "code\modules\mob\living\carbon\human\species_types\abductor.dm"
#include "code\modules\mob\living\carbon\human\species_types\android.dm"
#include "code\modules\mob\living\carbon\human\species_types\angel.dm"
-#include "code\modules\mob\living\carbon\human\species_types\anthromorph.dm"
+#include "code\modules\mob\living\carbon\human\species_types\anthropomorph.dm"
#include "code\modules\mob\living\carbon\human\species_types\arachnid.dm"
#include "code\modules\mob\living\carbon\human\species_types\bugmen.dm"
#include "code\modules\mob\living\carbon\human\species_types\corporate.dm"