diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index e9feb17043e..fe309999a47 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -453,6 +453,10 @@
if(gravitystate)
for(var/mob/living/carbon/human/M in A)
thunk(M)
+ for(var/obj/effect/decal/cleanable/blood/B in A)
+ B.splat(B)
+ for(var/obj/effect/decal/cleanable/vomit/V in A)
+ V.splat(V)
/area/proc/thunk(mob/living/carbon/human/M)
if(!istype(M)) // Rather not have non-humans get hit with a THUNK
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 753033dac89..f8b9af86ea4 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -981,6 +981,8 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons)
return
var/obj/effect/decal/cleanable/vomit/this = new type(src)
+ if(!this.gravity_check)
+ this.newtonian_move(dir)
// Make toxins vomit look different
if(toxvomit)
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index e92f196cd4c..98ca3badcda 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -1,4 +1,5 @@
#define DRYING_TIME 5 * 60 * 10 //for 1 unit of depth in puddle (amount var)
+#define ALWAYS_IN_GRAVITY 2
GLOBAL_LIST_EMPTY(splatter_cache)
@@ -21,9 +22,12 @@ GLOBAL_LIST_EMPTY(splatter_cache)
var/amount = 5
var/dry_timer = 0
var/off_floor = FALSE
-
+ var/image/weightless_image
+ inertia_move_delay = 1 // so they dont collide with who emitted them
/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C)
+ if(C == src)
+ return FALSE
if(C.blood_DNA)
blood_DNA |= C.blood_DNA.Copy()
if(bloodiness)
@@ -31,12 +35,15 @@ GLOBAL_LIST_EMPTY(splatter_cache)
C.bloodiness += bloodiness
return ..()
-
/obj/effect/decal/cleanable/blood/Initialize(mapload)
. = ..()
+ weightless_image = new()
update_icon()
- if(type == /obj/effect/decal/cleanable/blood/gibs)
+
+ if(!gravity_check)
+ //weightless blood cannot dry
return
+
if(!.)
dry_timer = addtimer(CALLBACK(src, PROC_REF(dry)), DRYING_TIME * (amount+1), TIMER_STOPPABLE)
@@ -46,9 +53,37 @@ GLOBAL_LIST_EMPTY(splatter_cache)
return ..()
/obj/effect/decal/cleanable/blood/update_icon()
+ var/turf/T = get_turf(src)
+ check_gravity(T)
+
+ if((T && (T.density)) || !gravity_check || locate(/obj/structure/window/) in T || locate(/obj/structure/grille/) in T)
+ off_floor = TRUE
+ layer = ABOVE_MOB_LAYER
+ plane = GAME_PLANE
+
if(basecolor == "rainbow")
basecolor = "#[pick(list("FF0000","FF7F00","FFFF00","00FF00","0000FF","4B0082","8F00FF"))]"
+
color = basecolor
+
+ if(!gravity_check)
+ if(prob(50))
+ animate_float(src, -1, rand(30,120))
+ else
+ animate_levitate(src, -1, rand(30,120))
+
+ if(weightless_image.icon_state)
+ icon_state = weightless_image.icon_state
+
+ overlays -= weightless_image
+ color = "#FFFFFF"
+ icon = 'icons/effects/blood_weightless.dmi'
+ weightless_image = image(icon, icon_state)
+ icon_state = "empty"
+ weightless_image.icon += basecolor
+ overlays += weightless_image
+ else
+ overlays.Cut()
..()
/obj/effect/decal/cleanable/blood/proc/dry()
@@ -56,6 +91,88 @@ GLOBAL_LIST_EMPTY(splatter_cache)
desc = drydesc
color = adjust_brightness(color, -50)
amount = 0
+ gravity_check = ALWAYS_IN_GRAVITY
+ animate(src)
+
+ if(isspaceturf(loc))
+ var/turf/T = get_turf(src)
+ if(!locate(/obj/structure/grille/) in T && !locate(/obj/structure/window/) in T)
+ qdel(src) //no free floating dried blood in space, thatd look weird
+
+/obj/effect/decal/cleanable/blood/ex_act()
+ . = ..()
+ update_icon()
+
+/obj/effect/decal/cleanable/blood/proc/splat(atom/AT)
+ if(gravity_check) //only floating blood can splat :C
+ return
+ var/turf/T = get_turf(AT)
+ if(try_merging_decal(T))
+ return
+ if(loc != T)
+ forceMove(T) //move to the turf to splatter on
+ animate(src) //stop floating
+ gravity_check = ALWAYS_IN_GRAVITY
+ icon = initial(icon)
+ icon_state = weightless_image.icon_state
+ layer = initial(layer)
+ plane = initial(plane)
+ update_icon()
+
+/obj/effect/decal/cleanable/blood/try_merging_decal(turf/T)
+ ..()
+
+/obj/effect/decal/cleanable/blood/Process_Spacemove(movement_dir)
+ if(gravity_check)
+ return TRUE
+
+ if(has_gravity(src))
+ if(!gravity_check)
+ splat(get_step(src, movement_dir))
+ return TRUE
+
+ if(pulledby && !pulledby.pulling)
+ return TRUE
+
+ if(throwing)
+ return TRUE
+
+ return FALSE
+
+
+/obj/effect/decal/cleanable/blood/Bump(atom/A, yes)
+ if(gravity_check)
+ return ..()
+ if(iswallturf(A) || istype(A, /obj/structure/window))
+ splat(A)
+ return
+ else if(A.density)
+ splat(get_turf(A))
+ return
+
+ if(ishuman(A))
+ bloodyify_human(A)
+ return
+
+ ..()
+
+/obj/effect/decal/cleanable/blood/proc/bloodyify_human(mob/living/carbon/human/H)
+ if(inertia_dir && H.inertia_dir == inertia_dir) //if they are moving the same direction we are, no collison
+ return
+
+ var/list/obj/item/things_to_potentially_bloody = list()
+ var/count = amount + 1
+
+ for(var/obj/item/i in H.contents)
+ things_to_potentially_bloody += i
+
+ if(length(things_to_potentially_bloody))
+ for(var/i in 1 to count)
+ things_to_potentially_bloody[rand(1, length(things_to_potentially_bloody))].add_blood(blood_DNA, basecolor)
+ count--
+ qdel(src)
+ else
+ splat(get_turf(H))
/obj/effect/decal/cleanable/blood/attack_hand(mob/living/carbon/human/user)
..()
@@ -144,6 +261,7 @@ GLOBAL_LIST_EMPTY(splatter_cache)
mergeable_decal = FALSE
var/image/giblets
var/fleshcolor = "#FFFFFF"
+ gravity_check = ALWAYS_IN_GRAVITY
/obj/effect/decal/cleanable/blood/gibs/Destroy()
giblets = null
@@ -218,3 +336,4 @@ GLOBAL_LIST_EMPTY(splatter_cache)
return FALSE
#undef DRYING_TIME
+#undef ALWAYS_IN_GRAVITY
diff --git a/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm b/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm
index 0ef4606ffdd..625c70555bf 100644
--- a/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm
+++ b/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm
@@ -1,3 +1,5 @@
+#define ALWAYS_IN_GRAVITY 3
+
/obj/effect/decal/cleanable/generic
name = "clutter"
desc = "Someone should clean that up."
@@ -141,12 +143,74 @@
gender = PLURAL
density = FALSE
layer = TURF_LAYER
+ plane = FLOOR_PLANE
icon = 'icons/effects/blood.dmi'
icon_state = "vomit_1"
random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4")
no_clear = TRUE
scoop_reagents = list("vomit" = 5)
+
+/obj/effect/decal/cleanable/vomit/Initialize(mapload)
+ . = ..()
+ var/turf/T = get_turf(src)
+ gravity_check = has_gravity(src, T)
+ if(loc != T)
+ forceMove(T)
+ if(!gravity_check)
+ layer = MOB_LAYER
+ plane = GAME_PLANE
+ if(prob(50))
+ animate_float(src, -1, rand(30, 120))
+ else
+ animate_levitate(src, -1, rand(30, 120))
+ icon = 'icons/effects/blood_weightless.dmi'
+
+/obj/effect/decal/cleanable/vomit/Bump(atom/A, yes)
+ . = ..()
+ if(A.density)
+ splat(A)
+
+/obj/effect/decal/cleanable/vomit/Crossed(atom/movable/AM, oldloc)
+ if(!gravity_check)
+ splat(AM)
+ ..()
+
+/obj/effect/decal/cleanable/vomit/proc/splat(atom/A)
+ if(gravity_check)
+ return
+ var/turf/T = get_turf(A)
+ if(try_merging_decal(T))
+ return
+ if(loc != T)
+ forceMove(T)
+ icon = initial(icon)
+ gravity_check = ALWAYS_IN_GRAVITY
+ if(T.density || locate(/obj/structure/window) in T)
+ layer = ABOVE_WINDOW_LAYER
+ plane = GAME_PLANE
+ else
+ layer = initial(layer)
+ plane = initial(plane)
+ animate(src)
+
+/obj/effect/decal/cleanable/vomit/Process_Spacemove(movement_dir)
+ if(gravity_check)
+ return 1
+
+ if(has_gravity(src))
+ if(!gravity_check)
+ splat(get_step(src, movement_dir))
+ return 1
+
+ if(pulledby && !pulledby.pulling)
+ return 1
+
+ if(throwing)
+ return 1
+
+ return 0
+
/obj/effect/decal/cleanable/vomit/green
name = "green vomit"
desc = "It's all gummy. Ew."
@@ -229,3 +293,5 @@
icon = 'icons/effects/blood.dmi'
icon_state = "xfloor1"
random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7")
+
+#undef ALWAYS_IN_GRAVITY
diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm
index b50635e67a3..9ebce846386 100644
--- a/code/game/objects/effects/decals/Cleanable/tracks.dm
+++ b/code/game/objects/effects/decals/Cleanable/tracks.dm
@@ -1,5 +1,6 @@
// 5 seconds
#define TRACKS_CRUSTIFY_TIME 50
+#define ALWAYS_IN_GRAVITY 2
// color-dir-dry
GLOBAL_LIST_EMPTY(fluidtrack_cache)
@@ -15,6 +16,7 @@ GLOBAL_LIST_EMPTY(fluidtrack_cache)
gender = PLURAL
random_icon_states = null
amount = 0
+ gravity_check = ALWAYS_IN_GRAVITY
//BLOODY FOOTPRINTS
/obj/effect/decal/cleanable/blood/footprints
@@ -28,7 +30,7 @@ GLOBAL_LIST_EMPTY(fluidtrack_cache)
var/exited_dirs = 0
var/base_alpha = BLOODY_FOOTPRINT_BASE_ALPHA
blood_state = BLOOD_STATE_HUMAN //the icon state to load images from
-
+ gravity_check = ALWAYS_IN_GRAVITY
/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O, oldloc)
..()
@@ -146,3 +148,5 @@ GLOBAL_LIST_EMPTY(fluidtrack_cache)
if(basecolor == COLOR_BLOOD_MACHINE)
return FALSE
return TRUE
+
+#undef ALWAYS_IN_GRAVITY
diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm
index 60550fa61e3..b3457fc2149 100644
--- a/code/game/objects/effects/decals/cleanable.dm
+++ b/code/game/objects/effects/decals/cleanable.dm
@@ -1,8 +1,16 @@
+#define ALWAYS_IN_GRAVITY 2
+
/obj/effect/decal/cleanable
+ ///when Initialized() its icon_state will be chosen from this list
var/list/random_icon_states = list()
- var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints
- var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one?
- plane = FLOOR_PLANE //prevents Ambient Occlusion effects around it ; Set to GAME_PLANE in Initialize() if on a wall
+ ///0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints
+ var/bloodiness = 0
+ ///when another of the same type is made on the same tile will they merge --- YES=TRUE; NO=FLASE
+ var/mergeable_decal = TRUE
+ ///prevents Ambient Occlusion effects around it ; Set to GAME_PLANE in Initialize() if on a wall
+ plane = FLOOR_PLANE
+ ///for blood n vomit in zero G --- IN GRAVITY=TRUE; NO GRAVITY=FALSE
+ var/gravity_check = TRUE
/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal
if(mergeable_decal)
@@ -20,7 +28,14 @@
//This is on /cleanable because fuck this ancient mess
/obj/effect/decal/cleanable/blood/Crossed(atom/movable/O)
..()
- if(!off_floor && ishuman(O))
+
+ if(!ishuman(O))
+ return
+
+ if(!gravity_check && ishuman(O))
+ bloodyify_human(O)
+
+ if(!off_floor)
var/mob/living/carbon/human/H = O
var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
@@ -71,12 +86,8 @@
/obj/effect/decal/cleanable/Initialize(mapload)
. = ..()
- if(loc && isturf(loc))
- for(var/obj/effect/decal/cleanable/C in loc)
- if(C != src && C.type == type && !QDELETED(C))
- if(replace_decal(C))
- qdel(src)
- return TRUE
+ if(try_merging_decal())
+ return TRUE
if(random_icon_states && length(src.random_icon_states) > 0)
src.icon_state = pick(src.random_icon_states)
if(smoothing_flags)
@@ -89,3 +100,22 @@
if(smoothing_flags)
QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
+
+/obj/effect/decal/cleanable/proc/try_merging_decal(turf/T)
+ if(!T)
+ T = loc
+ if(isturf(T))
+ for(var/obj/effect/decal/cleanable/C in T)
+ if(C != src && C.type == type && !QDELETED(C))
+ if(C.gravity_check && replace_decal(C))
+ qdel(src)
+ return TRUE
+ return FALSE
+
+/obj/effect/decal/cleanable/proc/check_gravity(turf/T)
+ if(isnull(T))
+ T = get_turf(src)
+ if(gravity_check != ALWAYS_IN_GRAVITY)
+ gravity_check = has_gravity(src, T)
+
+#undef ALWAYS_IN_GRAVITY
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 4ec0b242f0d..9b308992549 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -495,7 +495,7 @@ emp_act
bloody = 1
var/turf/location = loc
if(issimulatedturf(location))
- add_splatter_floor(location)
+ add_splatter_floor(location, emittor_intertia = inertia_next_move > world.time ? last_movement_dir : null)
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(get_dist(H, src) <= 1) //people with TK won't get smeared with blood
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 374a7339c2c..2124e289438 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1235,11 +1235,11 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
if(green)
if(!no_text)
visible_message("[src] vomits up some green goo!","You vomit up some green goo!")
- location.add_vomit_floor(FALSE, TRUE)
+ add_vomit_floor(FALSE, TRUE)
else
if(!no_text)
visible_message("[src] pukes all over [p_themselves()]!","You puke all over yourself!")
- location.add_vomit_floor(TRUE)
+ add_vomit_floor(TRUE)
/mob/proc/AddSpell(obj/effect/proc_holder/spell/S)
mob_spell_list += S
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index eb00d779687..150e792f070 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -156,6 +156,8 @@
direct = newdir
n = get_step(mob, direct)
+ mob.last_movement_dir = direct
+
var/prev_pulling_loc = null
if(mob.pulling)
prev_pulling_loc = mob.pulling.loc
diff --git a/code/modules/mob/mob_vars.dm b/code/modules/mob/mob_vars.dm
index 3d128502f52..d2e991a8e78 100644
--- a/code/modules/mob/mob_vars.dm
+++ b/code/modules/mob/mob_vars.dm
@@ -187,6 +187,9 @@
var/last_movement = -100 // Last world.time the mob actually moved of its own accord.
+ /// The direction they last moved
+ var/last_movement_dir
+
var/last_logout = 0
var/resize = 1 //Badminnery resize
diff --git a/code/modules/surgery/organs/blood.dm b/code/modules/surgery/organs/blood.dm
index cb6a7391559..f16ef7cecd8 100644
--- a/code/modules/surgery/organs/blood.dm
+++ b/code/modules/surgery/organs/blood.dm
@@ -87,9 +87,9 @@
blood_volume = max(blood_volume - amt, 0)
if(isturf(loc)) //Blood loss still happens in locker, floor stays clean
if(amt >= 10)
- add_splatter_floor(loc)
+ add_splatter_floor(loc, emittor_intertia = inertia_next_move > world.time ? last_movement_dir : null)
else
- add_splatter_floor(loc, 1)
+ add_splatter_floor(loc, 1, emittor_intertia = inertia_next_move > world.time ? last_movement_dir : null)
/mob/living/carbon/human/bleed(amt)
amt *= physiology.bleed_mod
@@ -108,7 +108,7 @@
blood_volume = max(blood_volume - amt, 0)
if(prob(10 * amt)) // +5% chance per internal bleeding site that we'll cough up blood on a given tick.
custom_emote(EMOTE_VISIBLE, "coughs up blood!")
- add_splatter_floor(loc, 1)
+ add_splatter_floor(loc, 1, emittor_intertia = inertia_next_move > world.time ? last_movement_dir : null)
return 1
else if(amt >= 1 && prob(5 * amt)) // +2.5% chance per internal bleeding site that we'll cough up blood on a given tick. Must be bleeding internally in more than one place to have a chance at this.
vomit(0, 1)
@@ -256,7 +256,7 @@
return list("O-", "O+")
//to add a splatter of blood or other mob liquid.
-/mob/living/proc/add_splatter_floor(turf/T, small_drip, shift_x, shift_y)
+/mob/living/proc/add_splatter_floor(turf/T, small_drip, shift_x, shift_y, emittor_intertia)
if((get_blood_id() != "blood") && (get_blood_id() != "slimejelly"))//is it blood or welding fuel?
return
if(!T)
@@ -269,9 +269,14 @@
// 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
if(drop)
+ if(emittor_intertia)
+ drop.newtonian_move(emittor_intertia)
if(drop.drips < 5)
drop.drips++
- drop.overlays |= pick(drop.random_icon_states)
+ var/image/I = image(drop.icon, drop.random_icon_states)
+ I.icon += drop.basecolor
+ drop.overlays |= I
+
drop.transfer_mob_blood_dna(src)
drop.basecolor = b_data["blood_color"]
drop.update_icon()
@@ -284,6 +289,8 @@
drop.transfer_mob_blood_dna(src)
drop.basecolor = b_data["blood_color"]
drop.update_icon()
+ if(emittor_intertia)
+ drop.newtonian_move(emittor_intertia)
return
// Find a blood decal or create a new one.
@@ -305,12 +312,14 @@
if(shift_x || shift_y)
B.off_floor = TRUE
B.layer = BELOW_MOB_LAYER //So the blood lands ontop of things like posters, windows, etc.
+ if(emittor_intertia)
+ B.newtonian_move(emittor_intertia)
-/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip, shift_x, shift_y)
+/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip, shift_x, shift_y, emittor_intertia)
if(!(NO_BLOOD in dna.species.species_traits))
..()
-/mob/living/carbon/alien/add_splatter_floor(turf/T, small_drip, shift_x, shift_y)
+/mob/living/carbon/alien/add_splatter_floor(turf/T, small_drip, shift_x, shift_y, emittor_intertia)
if(!T)
T = get_turf(src)
diff --git a/icons/effects/blood_weightless.dmi b/icons/effects/blood_weightless.dmi
new file mode 100644
index 00000000000..9d9e3ea92f7
Binary files /dev/null and b/icons/effects/blood_weightless.dmi differ