From 094ca3c73388e3f064cb8ad8d739cf4e299493b3 Mon Sep 17 00:00:00 2001 From: Kugamo Date: Wed, 14 Jun 2023 21:17:08 -0500 Subject: [PATCH] Blood now floats in zero G (#20993) * Da Vlood * removes_unused_icons * Yep sprites * fixes xeno blood and vomit * Commit number x * Steel Review Changes 1 * Henri Review Changes 1 * fixing vomit :volcano: * Haha haha ha ... ha * fixing layering issueeee * Sirryan2002 Review THANKS * implicit var/ * Sirryan Review 2 Electric Boogaloo * Testmerge Update 1 gibs were still getting animated, blood drying while animated in space looked very jank. all fixed * unused variable AHSDHASOIADGSOISA * Testmerge Update 2 tracks floating annoyingly * Improvements -blood globules will now continue with the inertia of their emittor, instead of towards wherever they were facing -drops will now have visible sprites after splatting [running animate() still sometimes doesnt stop the animation annoyingly] * errors * Sirryan Review 3 --- code/game/area/areas.dm | 4 + code/game/atoms.dm | 2 + .../effects/decals/Cleanable/humans.dm | 125 +++++++++++++++++- .../decals/Cleanable/misc_cleanables.dm | 66 +++++++++ .../effects/decals/Cleanable/tracks.dm | 6 +- code/game/objects/effects/decals/cleanable.dm | 50 +++++-- .../mob/living/carbon/human/human_defense.dm | 2 +- code/modules/mob/mob.dm | 4 +- code/modules/mob/mob_movement.dm | 2 + code/modules/mob/mob_vars.dm | 3 + code/modules/surgery/organs/blood.dm | 23 +++- icons/effects/blood_weightless.dmi | Bin 0 -> 5227 bytes 12 files changed, 263 insertions(+), 24 deletions(-) create mode 100644 icons/effects/blood_weightless.dmi 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 0000000000000000000000000000000000000000..9d9e3ea92f7b0a88b938d123d91a68050223065e GIT binary patch literal 5227 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 literal 0 HcmV?d00001