diff --git a/code/__DEFINES/generators.dm b/code/__DEFINES/generators.dm
new file mode 100644
index 000000000000..e9d373c9a65d
--- /dev/null
+++ b/code/__DEFINES/generators.dm
@@ -0,0 +1,15 @@
+//generator types
+#define GEN_NUM "num"
+#define GEN_VECTOR "vector"
+#define GEN_BOX "box"
+#define GEN_COLOR "color"
+#define GEN_CIRCLE "circle"
+#define GEN_SPHERE "sphere"
+#define GEN_SQUARE "square"
+#define GEN_CUBE "cube"
+
+///particle editor var modifiers
+#define P_DATA_GENERATOR "generator"
+#define P_DATA_ICON_ADD "icon_add"
+#define P_DATA_ICON_REMOVE "icon_remove"
+#define P_DATA_ICON_WEIGHT "icon_edit"
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index de8091fca186..9659ac17c64b 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -8,6 +8,8 @@
#define isweakref(D) (istype(D, /datum/weakref))
+#define isgenerator(A) (istype(A, /generator))
+
//Turfs
//#define isturf(A) (istype(A, /turf)) This is actually a byond built-in. Added here for completeness sake.
diff --git a/code/__DEFINES/particles.dm b/code/__DEFINES/particles.dm
new file mode 100644
index 000000000000..5657566a63bb
--- /dev/null
+++ b/code/__DEFINES/particles.dm
@@ -0,0 +1,5 @@
+// /obj/effect/abstract/particle_holder/var/particle_flags
+// Flags that effect how a particle holder displays something
+
+/// If we're inside something inside a mob, display off that mob too
+#define PARTICLE_ATTACH_MOB (1<<0)
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 5d9522b18ee4..602473b6086d 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -92,6 +92,7 @@
#define VV_HK_AUTO_RENAME "auto_rename"
#define VV_HK_RADIATE "radiate"
#define VV_HK_EDIT_FILTERS "edit_filters"
+#define VV_HK_EDIT_PARTICLES "edit_particles"
// /obj
#define VV_HK_OSAY "osay"
diff --git a/code/__HELPERS/generators.dm b/code/__HELPERS/generators.dm
new file mode 100644
index 000000000000..d50df7deba19
--- /dev/null
+++ b/code/__HELPERS/generators.dm
@@ -0,0 +1,11 @@
+/**
+ * returns the arguments given to a generator and manually extracts them from the internal byond object
+ * returns:
+ * * flat list of strings for args given to the generator.
+ * * Note: this means things like "list(1,2,3)" will need to be processed
+ */
+/proc/return_generator_args(generator/target)
+ var/string_repr = "[target]" //the name of the generator is the string representation of it's _binobj, which also contains it's args
+ string_repr = copytext(string_repr, 11, length(string_repr)) // strips extraneous data
+ string_repr = replacetext(string_repr, "\"", "") // removes the " around the type
+ return splittext(string_repr, ", ")
diff --git a/code/datums/looping_sounds/weather.dm b/code/datums/looping_sounds/weather.dm
index 4398e7d5b1b2..0e26b4592eb0 100644
--- a/code/datums/looping_sounds/weather.dm
+++ b/code/datums/looping_sounds/weather.dm
@@ -74,3 +74,20 @@
/datum/looping_sound/weather/rain/indoors
volume = 30
+
+/datum/looping_sound/weather/rain/storm
+ mid_sounds = list(
+ 'sound/ambience/storm_outdoors.ogg' = 1
+ )
+ mid_length = 20.03 SECONDS // The lengths for the files vary, but the longest is ten seconds, so this will make it sound like intermittent wind.
+ start_sound = 'sound/ambience/acidrain_start.ogg'
+ start_length = null
+ end_sound = null
+ volume = 50
+
+/datum/looping_sound/weather/rain/storm/indoors
+ volume = 30
+ mid_sounds = list(
+ 'sound/ambience/storm_indoors.ogg' = 1
+ )
+ mid_length = 20.03 SECONDS // The lengths for the files vary, but the longest is ten seconds, so this will make it sound like intermittent wind.
diff --git a/code/datums/weather/weather_types/rain.dm b/code/datums/weather/weather_types/rain.dm
index fbbb0269ed2a..591a569b19ec 100644
--- a/code/datums/weather/weather_types/rain.dm
+++ b/code/datums/weather/weather_types/rain.dm
@@ -47,3 +47,17 @@
desc = "Storm with rain and lightning."
weather_message = "The clouds blacken and the sky starts to flash as thunder strikes down!"
thunder_chance = 10
+
+/datum/weather/rain/heavy/storm_intense
+ name = "storm"
+ desc = "Storm with rain and lightning."
+ weather_overlay = "storm_very"
+ thunder_chance = 20
+ weather_color = "#a3daf7"
+ weather_duration_lower = 420690
+ weather_duration_upper = 420690
+
+ sound_active_outside = /datum/looping_sound/weather/rain/storm/indoors
+ sound_active_inside = /datum/looping_sound/weather/rain/storm
+ sound_weak_outside = /datum/looping_sound/weather/rain/storm/indoors
+ sound_weak_inside = /datum/looping_sound/weather/rain/storm
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 3dd327dda08c..5de5510bec7b 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -68,6 +68,8 @@
random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5")
dryname = "dried tracks"
drydesc = "Some old bloody tracks left by wheels. Machines are evil, perhaps."
+ ///Absorb the /squirt subtype when it exists on the turf
+ var/absorb_squirts = TRUE
/obj/effect/decal/cleanable/blood/tracks
icon_state = "tracks"
@@ -278,3 +280,132 @@
if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY))
return 1
return 0
+
+/obj/effect/decal/cleanable/blood/hitsplatter
+ name = "blood splatter"
+ pass_flags = PASSTABLE | PASSGRILLE
+ icon_state = "hitsplatter1"
+ random_icon_states = list("hitsplatter1", "hitsplatter2", "hitsplatter3")
+ /// The turf we just came from, so we can back up when we hit a wall
+ var/turf/prev_loc
+ /// The cached info about the blood
+ var/list/blood_dna_info
+ /// Skip making the final blood splatter when we're done, like if we're not in a turf
+ var/skip = FALSE
+ /// How many tiles/items/people we can paint red
+ var/splatter_strength = 3
+ /// Insurance so that we don't keep moving once we hit a stoppoint
+ var/hit_endpoint = FALSE
+// ///Absorb the /squirt subtype when it exists on the turf
+// var/absorb_squirts = TRUE
+
+/obj/effect/decal/cleanable/blood/hitsplatter/Initialize(mapload, splatter_strength)
+ . = ..()
+ prev_loc = loc //Just so we are sure prev_loc exists
+ if(splatter_strength)
+ src.splatter_strength = splatter_strength
+
+/obj/effect/decal/cleanable/blood/hitsplatter/Destroy()
+ if(isturf(loc) && !skip)
+ playsound(src, 'sound/effects/splatter.ogg', 60, TRUE, -1)
+ if(blood_dna_info)
+ loc.add_blood_DNA(blood_dna_info)
+ return ..()
+
+/// Set the splatter up to fly through the air until it rounds out of steam or hits something. Contains sleep() pending imminent moveloop rework, don't call without async'ing it
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/fly_towards(turf/target_turf, range)
+ splatter_strength = range
+ for(var/i in 1 to range)
+ step_towards(src,target_turf)
+ sleep(2) // Will be resolved pending Potato's moveloop rework
+ for(var/atom/iter_atom in get_turf(src))
+ if(hit_endpoint)
+ return
+ if(splatter_strength <= 0)
+ break
+
+ if(isitem(iter_atom))
+ iter_atom.add_blood_DNA(blood_dna_info)
+ splatter_strength--
+ else if(ishuman(iter_atom))
+ var/mob/living/carbon/human/splashed_human = iter_atom
+ if(splashed_human.wear_suit)
+ splashed_human.wear_suit.add_blood_DNA(blood_dna_info)
+ splashed_human.update_inv_wear_suit() //updates mob overlays to show the new blood (no refresh)
+ if(splashed_human.w_uniform)
+ splashed_human.w_uniform.add_blood_DNA(blood_dna_info)
+ splashed_human.update_inv_w_uniform() //updates mob overlays to show the new blood (no refresh)
+ splatter_strength--
+
+ if(splatter_strength <= 0) // we used all the puff so we delete it.
+ qdel(src)
+ return
+
+ var/obj/effect/decal/cleanable/blood/newsplatter
+ if(splatter_strength <= 3.5)
+ newsplatter = new /obj/effect/decal/cleanable/blood/squirt(get_turf(src), get_dir(prev_loc, loc), blood_dna_info)
+ else
+ newsplatter = new /obj/effect/decal/cleanable/blood/splatter(get_turf(src))
+ newsplatter.add_blood_DNA(blood_dna_info)
+ prev_loc = loc
+
+ qdel(src)
+ return
+
+/obj/effect/decal/cleanable/blood/hitsplatter/Bump(atom/bumped_atom)
+ if(!iswallturf(bumped_atom) && !istype(bumped_atom, /obj/structure/window))
+ qdel(src)
+ return
+
+ if(istype(bumped_atom, /obj/structure/window))
+ var/obj/structure/window/bumped_window = bumped_atom
+ if(!bumped_window.fulltile)
+ qdel(src)
+ return
+
+ hit_endpoint = TRUE
+ if(isturf(prev_loc))
+ abstract_move(bumped_atom)
+ skip = TRUE
+ //Adjust pixel offset to make splatters appear on the wall
+ if(istype(bumped_atom, /obj/structure/window))
+ land_on_window(bumped_atom)
+ else
+ var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new(prev_loc)
+ final_splatter.pixel_x = (dir == EAST ? 32 : (dir == WEST ? -32 : 0))
+ final_splatter.pixel_y = (dir == NORTH ? 32 : (dir == SOUTH ? -32 : 0))
+ else // This will only happen if prev_loc is not even a turf, which is highly unlikely.
+ abstract_move(bumped_atom)
+ qdel(src)
+
+/// A special case for hitsplatters hitting windows, since those can actually be moved around, store it in the window and slap it in the vis_contents
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/land_on_window(obj/structure/window/the_window)
+ if(!the_window.fulltile)
+ return
+ var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new
+ final_splatter.forceMove(the_window)
+ the_window.vis_contents += final_splatter
+ the_window.bloodied = TRUE
+ qdel(src)
+
+/obj/effect/decal/cleanable/blood/squirt
+ name = "blood trail"
+ icon_state = "squirt"
+ random_icon_states = null
+
+/obj/effect/decal/cleanable/blood/squirt/Initialize(mapload, direction, list/blood_dna)
+ . = ..()
+ dir = direction
+ var/obj/effect/decal/cleanable/blood/splatter/existing_blood = locate() in get_turf(src)
+ if(existing_blood?.absorb_squirts)
+ if(blood_dna)
+ existing_blood.add_blood_DNA(blood_dna)
+ existing_blood.bloodiness = min((existing_blood.bloodiness + bloodiness), BLOOD_AMOUNT_PER_DECAL)
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/decal/cleanable/blood/splatter/over_window // special layer/plane set to appear on windows
+ layer = ABOVE_WINDOW_LAYER
+ plane = GAME_PLANE
+ turf_loc_check = FALSE
+ alpha = 180
+ absorb_squirts = FALSE
diff --git a/code/game/objects/effects/particle_emitter.dm b/code/game/objects/effects/particle_emitter.dm
index 3ee4ab8ed461..cc4210f742a1 100644
--- a/code/game/objects/effects/particle_emitter.dm
+++ b/code/game/objects/effects/particle_emitter.dm
@@ -1,7 +1,70 @@
-/obj/effect/particle_emitter
- name = ""
+///objects can only have one particle on them at a time, so we use these abstract effects to hold and display the effects. You know, so multiple particle effects can exist at once.
+///also because some objects do not display particles due to how their visuals are built
+/obj/effect/abstract/particle_holder
+ name = "particle holder"
+ desc = "How are you reading this? Please make a bug report :)"
+ appearance_flags = KEEP_APART|KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE //movable appearance_flags plus KEEP_APART and KEEP_TOGETHER
+ vis_flags = VIS_INHERIT_PLANE
+ layer = ABOVE_ALL_MOB_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
- mouse_opacity = 0
+ /// Holds info about how this particle emitter works
+ /// See \code\__DEFINES\particles.dm
+ var/particle_flags = NONE
-/obj/effect/particle_emitter/Initialize(mapload, time)
+ var/atom/parent
+
+/obj/effect/abstract/particle_holder/Initialize(mapload, particle_path = /particles/smoke, particle_flags = NONE)
. = ..()
+ if(!loc)
+ stack_trace("particle holder was created with no loc!")
+ return INITIALIZE_HINT_QDEL
+ // We nullspace ourselves because some objects use their contents (e.g. storage) and some items may drop everything in their contents on deconstruct.
+ parent = loc
+ loc = null
+
+ // Mouse opacity can get set to opaque by some objects when placed into the object's contents (storage containers).
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ src.particle_flags = particle_flags
+ particles = new particle_path()
+ // /atom doesn't have vis_contents, /turf and /atom/movable do
+ var/atom/movable/lie_about_areas = parent
+ lie_about_areas.vis_contents += src
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(parent_deleted))
+
+ if(particle_flags & PARTICLE_ATTACH_MOB)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+ on_move(parent, null, NORTH)
+
+/obj/effect/abstract/particle_holder/Destroy(force)
+ QDEL_NULL(particles)
+ parent = null
+ return ..()
+
+/// Non movables don't delete contents on destroy, so we gotta do this
+/obj/effect/abstract/particle_holder/proc/parent_deleted(datum/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
+/// signal called when a parent that's been hooked into this moves
+/// does a variety of checks to ensure overrides work out properly
+/obj/effect/abstract/particle_holder/proc/on_move(atom/movable/attached, atom/oldloc, direction)
+ SIGNAL_HANDLER
+
+ if(!(particle_flags & PARTICLE_ATTACH_MOB))
+ return
+
+ //remove old
+ if(ismob(oldloc))
+ var/mob/particle_mob = oldloc
+ particle_mob.vis_contents -= src
+
+ // If we're sitting in a mob, we want to emit from it too, for vibes and shit
+ if(ismob(attached.loc))
+ var/mob/particle_mob = attached.loc
+ particle_mob.vis_contents += src
+
+/// Sets the particles position to the passed coordinate list (X, Y, Z)
+/// See [https://www.byond.com/docs/ref/#/{notes}/particles] for position documentation
+/obj/effect/abstract/particle_holder/proc/set_particle_position(list/pos)
+ particles.position = pos
diff --git a/code/game/objects/effects/particles/acid.dm b/code/game/objects/effects/particles/acid.dm
new file mode 100644
index 000000000000..5ce0984991d8
--- /dev/null
+++ b/code/game/objects/effects/particles/acid.dm
@@ -0,0 +1,15 @@
+// Acid related particles.
+/particles/acid
+ icon = 'icons/effects/particles/goop.dmi'
+ icon_state = list("goop_1" = 6, "goop_2" = 2, "goop_3" = 1)
+ width = 100
+ height = 100
+ count = 100
+ spawning = 0.5
+ color = "#00ea2b80" //to get 96 alpha
+ lifespan = 1.5 SECONDS
+ fade = 1 SECONDS
+ grow = -0.025
+ gravity = list(0, 0.15)
+ position = generator(GEN_SPHERE, 0, 16, NORMAL_RAND)
+ spin = generator(GEN_NUM, -15, 15, NORMAL_RAND)
diff --git a/code/game/objects/effects/particles/fire.dm b/code/game/objects/effects/particles/fire.dm
new file mode 100644
index 000000000000..fb20dc778e5e
--- /dev/null
+++ b/code/game/objects/effects/particles/fire.dm
@@ -0,0 +1,55 @@
+// Fire related particles.
+/particles/bonfire
+ icon = 'icons/effects/particles/bonfire.dmi'
+ icon_state = "bonfire"
+ width = 100
+ height = 100
+ count = 1000
+ spawning = 4
+ lifespan = 0.7 SECONDS
+ fade = 1 SECONDS
+ grow = -0.01
+ velocity = list(0, 0)
+ position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2))
+ gravity = list(0, 0.95)
+ scale = generator(GEN_VECTOR, list(0.3, 0.3), list(1,1), NORMAL_RAND)
+ rotation = 30
+ spin = generator(GEN_NUM, -20, 20)
+
+/particles/embers
+ icon = 'icons/effects/particles/generic.dmi'
+ icon_state = list("dot" = 4,"cross" = 1,"curl" = 1)
+ width = 64
+ height = 96
+ count = 500
+ spawning = 5
+ lifespan = 3 SECONDS
+ fade = 1 SECONDS
+ color = 0
+ color_change = 0.05
+ gradient = list("#FBAF4D", "#FCE6B6", "#FD481C")
+ position = generator(GEN_BOX, list(-12,-16,0), list(12,16,0), NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(-0.1,0), list(0.1,0.025))
+ spin = generator(GEN_NUM, list(-15,15), NORMAL_RAND)
+ scale = generator(GEN_VECTOR, list(0.5,0.5), list(2,2), NORMAL_RAND)
+
+/particles/embers/lava
+ width = 700
+ height = 700
+ gradient = list(LIGHT_COLOR_FLARE, LIGHT_COLOR_FLARE , COLOR_ALMOST_BLACK)
+ spawning = 1
+
+/particles/lava
+ width = 700
+ height = 700
+ count = 500
+ spawning = 1
+ lifespan = 4 SECONDS
+ fade = 2 SECONDS
+ position = generator(GEN_CIRCLE, 16, 24, NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(-0.2, -0.2), list(0.6, 0.6))
+ velocity = generator(GEN_CIRCLE, -6, 6, NORMAL_RAND)
+ friction = 0.15
+ gradient = list(0,LIGHT_COLOR_FLARE , 0.75, COLOR_ALMOST_BLACK)
+ color_change = 0.125
diff --git a/code/game/objects/effects/particles/misc.dm b/code/game/objects/effects/particles/misc.dm
new file mode 100644
index 000000000000..18db20d63798
--- /dev/null
+++ b/code/game/objects/effects/particles/misc.dm
@@ -0,0 +1,32 @@
+// General or un-matched particles, make a new file if a few can be sorted together.
+/particles/pollen
+ icon = 'icons/effects/particles/pollen.dmi'
+ icon_state = "pollen"
+ width = 100
+ height = 100
+ count = 1000
+ spawning = 4
+ lifespan = 0.7 SECONDS
+ fade = 1 SECONDS
+ grow = -0.01
+ velocity = list(0, 0)
+ position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2))
+ gravity = list(0, 0.95)
+ scale = generator(GEN_VECTOR, list(0.3, 0.3), list(1,1), NORMAL_RAND)
+ rotation = 30
+ spin = generator(GEN_NUM, -20, 20)
+
+/particles/echo
+ icon = 'icons/effects/particles/echo.dmi'
+ icon_state = list("echo1" = 1, "echo2" = 1, "echo3" = 2)
+ width = 480
+ height = 480
+ count = 1000
+ spawning = 0.5
+ lifespan = 2 SECONDS
+ fade = 1 SECONDS
+ gravity = list(0, -0.1)
+ position = generator(GEN_BOX, list(-240, -240), list(240, 240), NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(-0.1, 0), list(0.1, 0))
+ rotation = generator(GEN_NUM, 0, 360, NORMAL_RAND)
diff --git a/code/game/objects/effects/particles/slime.dm b/code/game/objects/effects/particles/slime.dm
new file mode 100644
index 000000000000..5cef9c976257
--- /dev/null
+++ b/code/game/objects/effects/particles/slime.dm
@@ -0,0 +1,22 @@
+/// Slime particles.
+/particles/slime
+ icon = 'icons/effects/particles/goop.dmi'
+ icon_state = list("goop_1" = 6, "goop_2" = 2, "goop_3" = 1)
+ width = 100
+ height = 100
+ count = 100
+ spawning = 0.5
+ color = "#707070a0"
+ lifespan = 1.5 SECONDS
+ fade = 1 SECONDS
+ grow = -0.025
+ gravity = list(0, -0.05)
+ position = generator(GEN_BOX, list(-8,-16,0), list(8,16,0), NORMAL_RAND)
+ spin = generator(GEN_NUM, -15, 15, NORMAL_RAND)
+ scale = list(0.75, 0.75)
+
+/// Rainbow slime particles.
+/particles/slime/rainbow
+ gradient = list(0, "#f00a", 3, "#0ffa", 6, "#f00a", "loop", "space"=COLORSPACE_HSL)
+ color_change = 0.2
+ color = generator(GEN_NUM, 0, 6, UNIFORM_RAND)
diff --git a/code/game/objects/effects/particles/smoke.dm b/code/game/objects/effects/particles/smoke.dm
new file mode 100644
index 000000000000..72807e778f56
--- /dev/null
+++ b/code/game/objects/effects/particles/smoke.dm
@@ -0,0 +1,66 @@
+// All the smoke variant particles.
+/particles/smoke
+ icon = 'icons/effects/particles/smoke.dmi'
+ icon_state = list("smoke_1" = 1, "smoke_2" = 1, "smoke_3" = 2)
+ width = 100
+ height = 100
+ count = 1000
+ spawning = 4
+ lifespan = 1.5 SECONDS
+ fade = 1 SECONDS
+ velocity = list(0, 0.4, 0)
+ position = list(6, 0, 0)
+ drift = generator(GEN_SPHERE, 0, 2, NORMAL_RAND)
+ friction = 0.2
+ gravity = list(0, 0.95)
+ grow = 0.05
+
+/particles/smoke/turf_fire
+ position = generator(GEN_SPHERE, 16, 24, NORMAL_RAND)
+
+/particles/smoke/burning
+ position = list(0, 0, 0)
+
+/particles/smoke/burning/small
+ spawning = 1
+ scale = list(0.8, 0.8)
+ velocity = list(0, 0.4, 0)
+
+/particles/smoke/steam
+ icon_state = list("steam_1" = 1, "steam_2" = 1, "steam_3" = 2)
+ fade = 1.5 SECONDS
+
+/particles/smoke/steam/mild
+ spawning = 1
+ velocity = list(0, 0.3, 0)
+ friction = 0.25
+
+/particles/smoke/steam/bad
+ icon_state = list("steam_1" = 1, "smoke_1" = 1, "smoke_2" = 1, "smoke_3" = 1)
+ spawning = 2
+ velocity = list(0, 0.25, 0)
+
+/particles/smoke/steam/vent
+ position = generator(GEN_SPHERE, 16, 16, NORMAL_RAND)
+ lifespan = 2 SECONDS
+ spawning = 3
+
+/particles/smoke/steam/vent/low
+ spawning = 1
+ velocity = list(0, 0.3, 0)
+ friction = 0.25
+
+/particles/smoke/steam/vent/high
+ spawning = 8
+ velocity = list(0, 0.25, 0)
+ count = 1000
+
+/particles/smoke/ash
+ icon_state = list("ash_1" = 2, "ash_2" = 2, "ash_3" = 1, "smoke_1" = 3, "smoke_2" = 2)
+ count = 500
+ spawning = 1
+ lifespan = 1 SECONDS
+ fade = 0.2 SECONDS
+ fadein = 0.7 SECONDS
+ position = generator(GEN_VECTOR, list(-3, 5, 0), list(3, 6.5, 0), NORMAL_RAND)
+ velocity = generator(GEN_VECTOR, list(-0.1, 0.4, 0), list(0.1, 0.5, 0), NORMAL_RAND)
diff --git a/code/game/objects/effects/particles/water.dm b/code/game/objects/effects/particles/water.dm
new file mode 100644
index 000000000000..456617cb5d8a
--- /dev/null
+++ b/code/game/objects/effects/particles/water.dm
@@ -0,0 +1,17 @@
+// Water related particles.
+/particles/droplets
+ icon = 'icons/effects/particles/generic.dmi'
+ icon_state = list("dot"=2,"drop"=1)
+ width = 32
+ height = 36
+ count = 5
+ spawning = 0.2
+ lifespan = 1 SECONDS
+ fade = 0.5 SECONDS
+ color = "#549EFF"
+ position = generator(GEN_BOX, list(-9,-9,0), list(9,18,0), NORMAL_RAND)
+ scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND)
+ gravity = list(0, -0.9)
+
+/particles/droplets/blood
+ spawning = 0.2
diff --git a/code/game/objects/effects/turf_fire.dm b/code/game/objects/effects/turf_fire.dm
index a0c9e0f95a9b..735d8226edfa 100644
--- a/code/game/objects/effects/turf_fire.dm
+++ b/code/game/objects/effects/turf_fire.dm
@@ -65,7 +65,7 @@
/obj/effect/abstract/turf_fire/Initialize(mapload, power, fire_color)
. = ..()
- particles = new /particles/lava
+ particles = new /particles/smoke/turf_fire
var/turf/open/open_turf = loc
if(open_turf.turf_fire)
return INITIALIZE_HINT_QDEL
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 78cfa10a2e0b..97b8c4348353 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -210,6 +210,7 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
take_damage(clamp(0.02 * exposed_temperature, 0, 20), BURN, "fire", 0)
if(!(resistance_flags & ON_FIRE) && (resistance_flags & FLAMMABLE) && !(resistance_flags & FIRE_PROOF))
resistance_flags |= ON_FIRE
+ burning_particles = new(src, /particles/smoke/burning)
SSfire_burning.processing[src] = src
update_appearance()
return 1
@@ -226,6 +227,7 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
if(resistance_flags & ON_FIRE)
resistance_flags &= ~ON_FIRE
update_appearance()
+ QDEL_NULL(burning_particles)
SSfire_burning.processing -= src
///Called when the obj is hit by a tesla bolt.
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index d4ad3f0e679e..0ffeaa673b53 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -43,6 +43,8 @@
vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of obj in openspace.
+ var/obj/effect/abstract/particle_holder/burning_particles
+
FASTDMM_PROP(\
pinned_vars = list("name", "dir")\
)
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index 0fca2bcca6ee..1698f90ec7cd 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -1056,3 +1056,39 @@
T.air.adjust_moles(GAS_CO2, -amt)
T.atmos_spawn_air("o2=[amt];TEMP=293.15")
lastcycle = world.time
+
+/obj/structure/fluff/steam_vent
+ name = "steam vent"
+ desc = "A outlet for steam, usually for water coming in contact with steam pipes."
+ icon = 'icons/obj/structures.dmi'
+ icon_state = "steamvent"
+ deconstructible = FALSE
+ layer = GAS_PUMP_LAYER
+
+ var/particle_to_spawn = /particles/smoke/steam/vent
+ var/obj/effect/particle_holder/part_hold
+
+/obj/structure/fluff/steam_vent/Initialize()
+ . = ..()
+ part_hold = new(get_turf(src))
+ part_hold.layer = EDGED_TURF_LAYER
+ part_hold.particles = new particle_to_spawn()
+ underlays.Cut()
+
+/obj/structure/fluff/steam_vent/Destroy()
+ . = ..()
+ QDEL_NULL(part_hold)
+
+/obj/structure/fluff/steam_vent/low
+ particle_to_spawn = /particles/smoke/steam/vent/low
+
+/obj/structure/fluff/steam_vent/high
+ particle_to_spawn = /particles/smoke/steam/vent/high
+
+/obj/effect/particle_holder
+ name = ""
+ anchored = TRUE
+ mouse_opacity = 0
+
+/obj/effect/particle_emitter/Initialize(mapload, time)
+ . = ..()
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 49c3823cf1ce..5420cc06b490 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -32,6 +32,9 @@
hitsound_type = PROJECTILE_HITSOUND_GLASS
+ /// If some inconsiderate jerk has had their blood spilled on this window, thus making it cleanable
+ var/bloodied = FALSE
+
/obj/structure/window/examine(mob/user)
. = ..()
if(flags_1 & NODECONSTRUCT_1)
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index eb2132940bd9..90fd6610721c 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -220,19 +220,13 @@
/turf/open/lava/smooth/airless
initial_gas_mix = AIRLESS_ATMOS
-/particles/lava
- width = 700
- height = 700
- count = 1000
- spawning = 1
- lifespan = 3 SECONDS
- fade = 2 SECONDS
- position = generator("circle", 16, 24, NORMAL_RAND)
- drift = generator("vector", list(-0.2, -0.2), list(0.2, 0.2))
- velocity = generator("circle", -6, 6, NORMAL_RAND)
- friction = 0.15
- gradient = list(0,LIGHT_COLOR_FLARE , 0.75, COLOR_ALMOST_BLACK)
- color_change = 0.125
+/obj/effect/particle_holder
+ name = ""
+ anchored = TRUE
+ mouse_opacity = 0
+
+/obj/effect/particle_emitter/Initialize(mapload, time)
+ . = ..()
/obj/effect/particle_emitter/lava
- particles = new/particles/lava
+ particles = new/particles/embers/lava
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 1910347e4fdd..8e2962e70d2f 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -51,19 +51,31 @@
if(BLOOD_VOLUME_MAXIMUM to BLOOD_VOLUME_EXCESS)
if(prob(10))
to_chat(src, "You feel terribly bloated.")
+
if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE)
- if(prob(5))
+
+ if(prob(1))
to_chat(src, "You feel [word].")
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.01, 1))
+ if(oxyloss < 20)
+ adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+
if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY)
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
- if(prob(5))
- blur_eyes(6)
+ if(eye_blurry < 50)
+ adjust_blurriness(5)
+ if(oxyloss < 40)
+ adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+ else
+ adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.01, 1))
+
+ if(prob(15))
+ Unconscious(rand(2 SECONDS,6 SECONDS))
to_chat(src, "You feel very [word].")
+
if(BLOOD_VOLUME_SURVIVE to BLOOD_VOLUME_BAD)
- adjustOxyLoss(5)
+ adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+ adjustToxLoss(2)
if(prob(15))
- Unconscious(rand(20,60))
+ Unconscious(rand(2 SECONDS,6 SECONDS))
to_chat(src, "You feel extremely [word].")
if(-INFINITY to BLOOD_VOLUME_SURVIVE)
if(!HAS_TRAIT(src, TRAIT_NODEATH))
@@ -81,25 +93,76 @@
BP.adjust_bleeding(0.1, BLOOD_LOSS_DAMAGE_MAXIMUM)
limb_bleed += BP.bleeding
+ var/message_cooldown = 5 SECONDS
+ var/bleeeding_wording
+// var/bleed_change_wording
+ switch(limb_bleed)
+ if(0 to 0.5)
+ bleeeding_wording = "You hear droplets of blood drip down."
+ message_cooldown *= 2.5
+ if(0.5 to 1)
+ bleeeding_wording = "You feel your blood flow quietly to the floor."
+ message_cooldown *= 2
+ if(1 to 2)
+ bleeeding_wording = "The flow of blood leaving your body onto the ground is worrying..."
+ message_cooldown *= 1.7
+ if(2 to 4)
+ bleeeding_wording = "You're losing blood very fast, which is freaking you out!"
+ message_cooldown *= 1.5
+ if(4 to INFINITY)
+ bleeeding_wording = "Your heartbeat beats unstably fast as you lose a massive amount of blood!!"
+
if(limb_bleed && !bleedsuppress && !HAS_TRAIT(src, TRAIT_FAKEDEATH))
bleed(limb_bleed)
+ if(!blood_particle)
+ blood_particle = new(src, /particles/droplets/blood, PARTICLE_ATTACH_MOB)
+ blood_particle.particles.color = dna.blood_type.color //mouthful
+ blood_particle.particles.spawning = (limb_bleed/2)
+ blood_particle.particles.count = (round(clamp((limb_bleed * 2), 1, INFINITY)))
+
+ if(COOLDOWN_FINISHED(src, bloodloss_message) && bleeeding_wording)
+ to_chat(src, span_warning("[bleeeding_wording]"))
+ COOLDOWN_START(src, bloodloss_message, message_cooldown)
+ else
+ if(blood_particle)
+ QDEL_NULL(blood_particle)
+
//Makes a blood drop, leaking amt units of blood from the mob
/mob/living/carbon/proc/bleed(amt)
if(blood_volume)
blood_volume = max(blood_volume - amt, 0)
if (prob(sqrt(amt)*BLOOD_DRIP_RATE_MOD))
if(isturf(src.loc) && !isgroundlessturf(src.loc)) //Blood loss still happens in locker, floor stays clean
- if(amt >= 10)
- add_splatter_floor(src.loc)
+ if(amt >= 2)
+ add_splatter_floor(src.loc, amt = amt)
else
- add_splatter_floor(src.loc, 1)
+ add_splatter_floor(src.loc, TRUE, amt)
/mob/living/carbon/human/bleed(amt)
amt *= physiology.bleed_mod
if(!(NOBLOOD in dna.species.species_traits))
..()
+/**
+ * This proc is a helper for spraying blood for things like slashing/piercing wounds and dismemberment.
+ *
+ * The strength of the splatter in the second argument determines how much it can dirty and how far it can go
+ *
+ * Arguments:
+ * * splatter_direction: Which direction the blood is flying
+ * * splatter_strength: How many tiles it can go, and how many items it can pass over and dirty
+ */
+/mob/living/carbon/proc/spray_blood(splatter_direction, splatter_strength = 3)
+ if(!isturf(loc))
+ return
+ var/obj/effect/decal/cleanable/blood/hitsplatter/our_splatter = new(loc)
+
+// our_splatter.transfer_mob_blood_dna(return_blood_DNA(src))
+ our_splatter.blood_dna_info = get_blood_dna_list()
+ our_splatter.transfer_mob_blood_dna(src)
+ var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
+ INVOKE_ASYNC(our_splatter, TYPE_PROC_REF(/obj/effect/decal/cleanable/blood/hitsplatter, fly_towards), targ, splatter_strength)
/mob/living/proc/restore_blood()
@@ -229,13 +292,14 @@
return blood_type.color
//to add a splatter of blood or other mob liquid.
-/mob/living/proc/add_splatter_floor(turf/T, small_drip)
+/mob/living/proc/add_splatter_floor(turf/T, small_drip, amt)
if(get_blood_id() != /datum/reagent/blood)
return
if(!T)
T = get_turf(src)
var/list/temp_blood_DNA
+
if(small_drip)
// Only a certain number of drips (or one large splatter) can be on a given turf.
var/obj/effect/decal/cleanable/blood/drip/drop = locate() in T
@@ -248,7 +312,7 @@
else
temp_blood_DNA = drop.return_blood_DNA() //we transfer the dna from the drip to the splatter
qdel(drop)//the drip is replaced by a bigger splatter
- else
+ else if (amt < 2)
drop = new(T, get_static_viruses())
drop.transfer_mob_blood_dna(src)
return
@@ -261,7 +325,11 @@
B = candidate
break
if(!B)
- B = new /obj/effect/decal/cleanable/blood/splatter(T, get_static_viruses())
+ if(amt > 4)
+ B = new /obj/effect/decal/cleanable/blood(T, get_static_viruses())
+ else
+ B = new /obj/effect/decal/cleanable/blood/splatter(T, get_static_viruses())
+
if(QDELETED(B)) //Give it up
return
B.bloodiness = min((B.bloodiness + BLOOD_AMOUNT_PER_DECAL), BLOOD_POOL_MAX)
@@ -269,11 +337,11 @@
if(temp_blood_DNA)
B.add_blood_DNA(temp_blood_DNA)
-/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip)
+/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip, amt)
if(!(NOBLOOD in dna.species.species_traits))
..()
-/mob/living/carbon/alien/add_splatter_floor(turf/T, small_drip)
+/mob/living/carbon/alien/add_splatter_floor(turf/T, small_drip, amt)
if(!T)
T = get_turf(src)
var/obj/effect/decal/cleanable/xenoblood/B = locate() in T.contents
@@ -281,7 +349,7 @@
B = new(T)
B.add_blood_DNA(list("UNKNOWN DNA" = "X*"))
-/mob/living/silicon/robot/add_splatter_floor(turf/T, small_drip)
+/mob/living/silicon/robot/add_splatter_floor(turf/T, small_drip, amt)
if(!T)
T = get_turf(src)
var/obj/effect/decal/cleanable/oil/B = locate() in T.contents
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 43cefa251e34..6a34e87fdf42 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -653,3 +653,20 @@
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
else if(getOxyLoss() <= 50)
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, OXYLOSS_TRAIT)
+
+/mob/living/carbon/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
+ var/mob/living/carbon/human/current_user = src //is this a good idea? who can say?
+ var/armor = run_armor_check(def_zone, P.flag, P.armour_penetration, silent = TRUE)
+ var/on_hit_state = P.on_hit(src, armor, piercing_hit)
+ if(!P.nodamage && on_hit_state != BULLET_ACT_BLOCK && !QDELETED(src)) //QDELETED literally just for the instagib rifle. Yeah.
+ apply_damage(P.damage, P.damage_type, def_zone, armor, sharpness = TRUE)
+ if(P.damage-armor >= 15 && P.damage_type == BRUTE && (!armor || prob(40) || P.damage-armor >= 25))
+ spray_blood(get_dir(P.starting,src), (P.damage-armor)/5)
+ var/obj/item/bodypart/targeted_bodypart = null
+ bleed((P.damage-armor)/2)
+
+ recoil_camera(src, clamp((P.damage-armor)/4,0.5,10), clamp((P.damage-armor)/4,0.5,10), P.damage/8, P.Angle)
+ apply_effects(P.stun, P.knockdown, P.unconscious, P.irradiate, P.slur, P.stutter, P.eyeblur, P.drowsy, armor, P.stamina, P.jitter, P.paralyze, P.immobilize)
+ if(P.dismemberment)
+ check_projectile_dismemberment(P, def_zone)
+ return on_hit_state ? BULLET_ACT_HIT : BULLET_ACT_BLOCK
diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm
index 8c1a36c2061b..1804a1497187 100644
--- a/code/modules/mob/living/carbon/death.dm
+++ b/code/modules/mob/living/carbon/death.dm
@@ -31,6 +31,9 @@
if(prob(50))
step(W, pick(GLOB.alldirs))
var/atom/Tsec = drop_location()
+ var/amount_of_streams_to_spawn = rand(2,4)
+ for(var/i in 1 to amount_of_streams_to_spawn)
+ spray_blood(pick(GLOB.alldirs), rand(1,6))
for(var/mob/M in src)
M.forceMove(Tsec)
visible_message("[M] bursts out of [src]!")
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 567523c11d79..df006ead1f39 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -77,3 +77,7 @@
/// How many "units of blood" we have on our hands
var/blood_in_hands = 0
+ ///blood particle effect
+ var/obj/effect/abstract/particle_holder/blood_particle
+
+ COOLDOWN_DECLARE(bloodloss_message)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index 6615edb051f0..0687d90da75b 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -1,4 +1,4 @@
-/mob/living/gib(no_brain, no_organs, no_bodyparts)
+/mob/living/gib(no_brain, no_organs, no_bodyparts, safe_gib = FALSE)
var/prev_lying = lying_angle
if(stat != DEAD)
death(TRUE)
@@ -12,7 +12,8 @@
spread_bodyparts(no_brain, no_organs)
spawn_gibs(no_bodyparts)
- qdel(src)
+ if(!safe_gib)
+ qdel(src)
/mob/living/proc/gib_animation()
return
diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm
index f8d8a9a49384..0431ae9aad73 100644
--- a/code/modules/surgery/bodyparts/bodyparts.dm
+++ b/code/modules/surgery/bodyparts/bodyparts.dm
@@ -777,7 +777,11 @@
if (bone_status == BONE_FLAG_NORMAL && body_part & LEGS) // Because arms are not legs
owner.set_broken_legs(owner.broken_legs + 1)
bone_status = BONE_FLAG_BROKEN
- addtimer(CALLBACK(owner, TYPE_PROC_REF(/atom, visible_message), "You hear a cracking sound coming from [owner]'s [name].", "You feel something crack in your [name]!", "You hear an awful cracking sound."), 1 SECONDS)
+// addtimer(CALLBACK(src, PROC_REF(break_bone_feedback), 1 SECONDS)) testing sommething
+
+///obj/item/bodypart/proc/break_bone_feedback()
+ owner.visible_message("You hear a cracking sound coming from [owner]'s [name].", "You feel something crack in your [name]!", "You hear an awful cracking sound.")
+ playsound(owner, list('sound/health/bone/bone_break1.ogg','sound/health/bone/bone_break2.ogg','sound/health/bone/bone_break3.ogg','sound/health/bone/bone_break4.ogg','sound/health/bone/bone_break5.ogg','sound/health/bone/bone_break6.ogg'), 100, FALSE, -1)
/obj/item/bodypart/proc/fix_bone()
// owner.update_inv_splints() breaks
diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi
index f7e2e158d422..aed7e9b4fbf4 100644
Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ
diff --git a/icons/effects/particles/bonfire.dmi b/icons/effects/particles/bonfire.dmi
new file mode 100644
index 000000000000..e8e2e36346da
Binary files /dev/null and b/icons/effects/particles/bonfire.dmi differ
diff --git a/icons/effects/particles/echo.dmi b/icons/effects/particles/echo.dmi
new file mode 100644
index 000000000000..60a243a8a7be
Binary files /dev/null and b/icons/effects/particles/echo.dmi differ
diff --git a/icons/effects/particles/generic.dmi b/icons/effects/particles/generic.dmi
new file mode 100644
index 000000000000..dfbb1a47a6ef
Binary files /dev/null and b/icons/effects/particles/generic.dmi differ
diff --git a/icons/effects/particles/goop.dmi b/icons/effects/particles/goop.dmi
new file mode 100644
index 000000000000..673c1a7ad5b6
Binary files /dev/null and b/icons/effects/particles/goop.dmi differ
diff --git a/icons/effects/particles/pollen.dmi b/icons/effects/particles/pollen.dmi
new file mode 100644
index 000000000000..559c4d1846f6
Binary files /dev/null and b/icons/effects/particles/pollen.dmi differ
diff --git a/icons/effects/particles/smoke.dmi b/icons/effects/particles/smoke.dmi
new file mode 100644
index 000000000000..4a3239499b96
Binary files /dev/null and b/icons/effects/particles/smoke.dmi differ
diff --git a/icons/effects/weather_effects.dmi b/icons/effects/weather_effects.dmi
index a8a7185af500..f76e34ec71ec 100644
Binary files a/icons/effects/weather_effects.dmi and b/icons/effects/weather_effects.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index f5f04901af2a..af3c5cd4be2e 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/shiptest.dme b/shiptest.dme
index 0686ea00c049..09e6f8be9c16 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -67,6 +67,7 @@
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
#include "code\__DEFINES\forensics.dm"
+#include "code\__DEFINES\generators.dm"
#include "code\__DEFINES\guns.dm"
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\icon_smoothing.dm"
@@ -105,6 +106,7 @@
#include "code\__DEFINES\obj_flags.dm"
#include "code\__DEFINES\overmap.dm"
#include "code\__DEFINES\paper.dm"
+#include "code\__DEFINES\particles.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
#include "code\__DEFINES\plumbing.dm"
@@ -190,6 +192,7 @@
#include "code\__HELPERS\files.dm"
#include "code\__HELPERS\filters.dm"
#include "code\__HELPERS\game.dm"
+#include "code\__HELPERS\generators.dm"
#include "code\__HELPERS\global_lists.dm"
#include "code\__HELPERS\heap.dm"
#include "code\__HELPERS\icon_smoothing.dm"
@@ -1121,6 +1124,12 @@
#include "code\game\objects\effects\effect_system\effects_smoke.dm"
#include "code\game\objects\effects\effect_system\effects_sparks.dm"
#include "code\game\objects\effects\effect_system\effects_water.dm"
+#include "code\game\objects\effects\particles\acid.dm"
+#include "code\game\objects\effects\particles\fire.dm"
+#include "code\game\objects\effects\particles\misc.dm"
+#include "code\game\objects\effects\particles\slime.dm"
+#include "code\game\objects\effects\particles\smoke.dm"
+#include "code\game\objects\effects\particles\water.dm"
#include "code\game\objects\effects\spawners\bombspawner.dm"
#include "code\game\objects\effects\spawners\bundle.dm"
#include "code\game\objects\effects\spawners\gibspawner.dm"
diff --git a/sound/ambience/storm_indoors.ogg b/sound/ambience/storm_indoors.ogg
new file mode 100644
index 000000000000..62e9014a05ff
Binary files /dev/null and b/sound/ambience/storm_indoors.ogg differ
diff --git a/sound/ambience/storm_outdoors.ogg b/sound/ambience/storm_outdoors.ogg
new file mode 100644
index 000000000000..35ae8e5297d6
Binary files /dev/null and b/sound/ambience/storm_outdoors.ogg differ
diff --git a/sound/effects/splatter.ogg b/sound/effects/splatter.ogg
new file mode 100644
index 000000000000..1c678cfe1268
Binary files /dev/null and b/sound/effects/splatter.ogg differ
diff --git a/sound/health/bone/bone_break1.ogg b/sound/health/bone/bone_break1.ogg
new file mode 100644
index 000000000000..dd2d22ec792b
Binary files /dev/null and b/sound/health/bone/bone_break1.ogg differ
diff --git a/sound/health/bone/bone_break2.ogg b/sound/health/bone/bone_break2.ogg
new file mode 100644
index 000000000000..aa2537f894de
Binary files /dev/null and b/sound/health/bone/bone_break2.ogg differ
diff --git a/sound/health/bone/bone_break3.ogg b/sound/health/bone/bone_break3.ogg
new file mode 100644
index 000000000000..9f66324be3b2
Binary files /dev/null and b/sound/health/bone/bone_break3.ogg differ
diff --git a/sound/health/bone/bone_break4.ogg b/sound/health/bone/bone_break4.ogg
new file mode 100644
index 000000000000..bbdfac1ecff3
Binary files /dev/null and b/sound/health/bone/bone_break4.ogg differ
diff --git a/sound/health/bone/bone_break5.ogg b/sound/health/bone/bone_break5.ogg
new file mode 100644
index 000000000000..dfee0e9baa72
Binary files /dev/null and b/sound/health/bone/bone_break5.ogg differ
diff --git a/sound/health/bone/bone_break6.ogg b/sound/health/bone/bone_break6.ogg
new file mode 100644
index 000000000000..d41cc8d7cf54
Binary files /dev/null and b/sound/health/bone/bone_break6.ogg differ