diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 447c93f6713..9945149811c 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -252,8 +252,7 @@
apply_damage(damage, attacking_item.damtype, attacking_item = attacking_item)
if(attacking_item.damtype == BRUTE && prob(33))
attacking_item.add_mob_blood(src)
- var/turf/location = get_turf(src)
- add_splatter_floor(location)
+ blood_particles(amount = rand(0,1), angle = (user == src ? rand(0, 360) : get_angle(user, src)))
if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood
user.add_mob_blood(src)
return TRUE //successful attack
diff --git a/code/datums/components/movable_physics.dm b/code/datums/components/movable_physics.dm
index d8d3011eab7..60ff150e0c8 100644
--- a/code/datums/components/movable_physics.dm
+++ b/code/datums/components/movable_physics.dm
@@ -62,12 +62,14 @@
var/bounce_sound
/// If we have this callback, it gets invoked when bouncing on the floor
var/datum/callback/bounce_callback
+ /// If we have this callback, it gets invoked when bumping on another atom
+ var/datum/callback/bump_callback
/// If we have this callback, it gets invoked when stopping movement
var/datum/callback/stop_callback
/**
* The cached animate_movement of the parent
- * Any kind of gliding when doing Move() makes the physics look derpy, so we'll just make Move() be instant
+ * Any kind of gliding when doing Move() makes the physics look derpy, so we'll just make Move() be "instant"
*/
var/cached_animate_movement
/// Cached transform of the parent, in case some fucking idiot decides its a good idea to make the damn movable spin forever
@@ -94,6 +96,7 @@
bounce_spin_clockwise = 0,
bounce_sound,
bounce_callback,
+ bump_callback,
stop_callback,
)
if(!ismovable(parent))
@@ -117,6 +120,7 @@
src.bounce_spin_clockwise = bounce_spin_clockwise
src.bounce_sound = bounce_sound
src.bounce_callback = bounce_callback
+ src.bump_callback = bump_callback
src.stop_callback = stop_callback
set_angle(angle)
@@ -174,6 +178,10 @@
else if(moving_atom.pixel_z <= z_floor && vertical_velocity)
z_floor_bounce(moving_atom)
+ // z_floor_bounce could have deleted us
+ if(QDELETED(src))
+ return
+
visual_angle_velocity = max(0, visual_angle_velocity - visual_angle_friction)
var/move_direction = NONE
@@ -232,7 +240,7 @@
var/atom/movable/moving_atom = parent
cached_animate_movement = moving_atom.animate_movement
moving_atom.animate_movement = NO_STEPS
- if(!spin_speed || visual_angle_velocity || visual_angle_friction)
+ if(!spin_speed || !spin_loops || visual_angle_velocity || visual_angle_friction)
return
moving_atom.SpinAnimation(speed = spin_speed, loops = spin_loops)
if(spin_loops == INFINITE)
@@ -277,11 +285,12 @@
var/incidence = GET_ANGLE_OF_INCIDENCE(face_angle, angle + 180)
var/new_angle = SIMPLIFY_DEGREES(face_angle + incidence)
set_angle(new_angle)
- if(!visual_angle_velocity)
- return
- incidence = GET_ANGLE_OF_INCIDENCE(face_angle, source.visual_angle + 180)
- new_angle = SIMPLIFY_DEGREES(face_angle + incidence)
- source.set_visual_angle(new_angle)
+ if(visual_angle_velocity)
+ incidence = GET_ANGLE_OF_INCIDENCE(face_angle, source.visual_angle + 180)
+ new_angle = SIMPLIFY_DEGREES(face_angle + incidence)
+ source.set_visual_angle(new_angle)
+ if(bump_callback)
+ bump_callback.Invoke(bumped_atom)
/// Stops movement for pesky items when they get picked up, as that essentially invalidates this component
/datum/component/movable_physics/proc/on_item_pickup(obj/item/source)
@@ -300,7 +309,7 @@
var/angle_of_movement = angle_to_target
if(deviation)
angle_of_movement += SIMPLIFY_DEGREES(rand(-deviation * 100, deviation * 100) * 0.01)
- AddComponent(/datum/component/movable_physics, \
+ return AddComponent(/datum/component/movable_physics, \
angle = angle_of_movement, \
horizontal_velocity = rand(4.5 * 100, 5.5 * 100) * 0.01, \
vertical_velocity = rand(4 * 100, 4.5 * 100) * 0.01, \
diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm
index 5fb2cdf2a3c..1e8c1ac8f43 100644
--- a/code/datums/mutable_appearance.dm
+++ b/code/datums/mutable_appearance.dm
@@ -7,7 +7,7 @@
// Mutable appearances erase template vars on new, because they accept an appearance to copy as an arg
// If we have nothin to copy, we set the float plane
/mutable_appearance/New(mutable_appearance/to_copy)
- ..()
+ . = ..()
if(!to_copy)
plane = FLOAT_PLANE
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index 65deeff4e73..481b04592c6 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -491,7 +491,7 @@
owner.visible_message(span_warning("[owner]'s head splatters with a sickening crunch!"), ignored_mobs = list(owner))
new /obj/effect/gibspawner/generic(get_turf(owner), owner)
head.dismember(BRUTE)
- head.drop_organs()
+ head.drop_organs(violent_removal = TRUE)
qdel(head)
owner.regenerate_icons()
RegisterSignal(owner, COMSIG_ATTEMPT_CARBON_ATTACH_LIMB, PROC_REF(abortattachment))
diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm
index da8812868dc..f79ff1669eb 100644
--- a/code/datums/wounds/_wounds.dm
+++ b/code/datums/wounds/_wounds.dm
@@ -584,7 +584,7 @@
/// Used when we're being dragged while bleeding, the value we return is how much bloodloss this wound causes from being dragged. Since it's a proc, you can let bandages soak some of the blood
/datum/wound/proc/drag_bleed_amount()
- return
+ return 0
/**
* get_bleed_rate_of_change() is used in [/mob/living/carbon/proc/bleed_warn] to gauge whether this wound (if bleeding) is becoming worse, better, or staying the same over time
diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm
index 61e63fb8122..ffb6939a40d 100644
--- a/code/datums/wounds/bones.dm
+++ b/code/datums/wounds/bones.dm
@@ -143,30 +143,31 @@
/datum/wound/blunt/bone/receive_damage(wounding_type, wounding_dmg, wound_bonus)
- if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE)
+ if(!victim || (wounding_dmg < WOUND_MINIMUM_DAMAGE) || (victim.blood_volume <= 0) || !prob(internal_bleeding_chance + wounding_dmg))
return
- if(ishuman(victim))
- var/mob/living/carbon/human/human_victim = victim
- if(HAS_TRAIT(human_victim, TRAIT_NOBLOOD))
- return
- if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg))
- var/blood_bled = rand(1, wounding_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5)) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound
+ if(limb.body_zone == BODY_ZONE_CHEST)
+ var/blood_bled = rand(1, round(wounding_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5))) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound
switch(blood_bled)
if(1 to 6)
- victim.bleed(blood_bled, TRUE)
+ victim.bleed(blood_bled)
if(7 to 13)
- victim.visible_message("A thin stream of blood drips from [victim]'s mouth from the blow to [victim.p_their()] chest.", span_danger("You cough up a bit of blood from the blow to your chest."), vision_distance=COMBAT_MESSAGE_RANGE)
- victim.bleed(blood_bled, TRUE)
- if(14 to 19)
- victim.visible_message("Blood spews out of [victim]'s mouth from the blow to [victim.p_their()] chest!", span_danger("You spit out a string of blood from the blow to your chest!"), vision_distance=COMBAT_MESSAGE_RANGE)
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
+ victim.visible_message(span_danger("A thin stream of blood drips from [victim]'s mouth from the blow to [victim.p_their()] chest."), \
+ span_danger("You cough up a bit of blood from the blow to your chest."), \
+ vision_distance = COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled)
+ if(14 to 19)
+ victim.visible_message(span_danger("Blood spews out of [victim]'s mouth from the blow to [victim.p_their()] chest!"), \
+ span_danger("You spit out a string of blood from the blow to your chest!"), \
+ vision_distance = COMBAT_MESSAGE_RANGE)
+ victim.bleed(blood_bled, no_visual = TRUE)
+ victim.blood_particles(amount = 1)
if(20 to INFINITY)
- victim.visible_message(span_danger("Blood spurts out of [victim]'s mouth from the blow to [victim.p_their()] chest!"), span_danger("You choke up on a spray of blood from the blow to your chest!"), vision_distance=COMBAT_MESSAGE_RANGE)
- victim.bleed(blood_bled)
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
- victim.add_splatter_floor(get_step(victim.loc, victim.dir))
+ victim.visible_message(span_danger("Blood spurts out of [victim]'s mouth from the blow to [victim.p_their()] chest!"), \
+ span_danger("You choke up on a spray of blood from the blow to your chest!"), \
+ vision_distance = COMBAT_MESSAGE_RANGE)
+ victim.bleed(blood_bled, no_visual = TRUE)
+ victim.blood_particles(amount = rand(0, 1))
/datum/wound/blunt/bone/modify_desc_before_span(desc)
. = ..()
diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm
index a0d0190f568..46b8f8dd2dd 100644
--- a/code/datums/wounds/loss.dm
+++ b/code/datums/wounds/loss.dm
@@ -49,8 +49,9 @@
set_limb(dismembered_part)
second_wind()
log_wound(victim, src)
- if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN && victim.blood_volume)
+ if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN)
victim.spray_blood(attack_direction, severity)
+ victim.blood_particles(amount = rand(1, 3), angle = 0, min_deviation = 0, max_deviation = 360)
dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE, wounding_type = wounding_type)
qdel(src)
return TRUE
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
index a1daf2296c7..34913c9acf8 100644
--- a/code/datums/wounds/pierce.dm
+++ b/code/datums/wounds/pierce.dm
@@ -42,14 +42,20 @@
if(1 to 6)
victim.bleed(blood_bled, TRUE)
if(7 to 13)
- victim.visible_message("Blood droplets fly from the hole in [victim]'s [limb.plaintext_zone].", span_danger("You cough up a bit of blood from the blow to your [limb.plaintext_zone]."), vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.visible_message("Blood droplets fly from the hole in [victim]'s [limb.plaintext_zone].", \
+ span_danger("You cough up a bit of blood from the blow to your [limb.plaintext_zone]."), \
+ vision_distance = COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled, TRUE)
if(14 to 19)
- victim.visible_message("A small stream of blood spurts from the hole in [victim]'s [limb.plaintext_zone]!", span_danger("You spit out a string of blood from the blow to your [limb.plaintext_zone]!"), vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.visible_message("A small stream of blood spurts from the hole in [victim]'s [limb.plaintext_zone]!", \
+ span_danger("You spit out a string of blood from the blow to your [limb.plaintext_zone]!"), \
+ vision_distance = COMBAT_MESSAGE_RANGE)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.bleed(blood_bled)
if(20 to INFINITY)
- victim.visible_message(span_danger("A spray of blood streams from the gash in [victim]'s [limb.plaintext_zone]!"), span_danger("You choke up on a spray of blood from the blow to your [limb.plaintext_zone]!"), vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.visible_message(span_danger("A spray of blood streams from the gash in [victim]'s [limb.plaintext_zone]!"), \
+ span_danger("You choke up on a spray of blood from the blow to your [limb.plaintext_zone]!"), \
+ vision_distance = COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm
index e31b8d21e5c..487310fa603 100644
--- a/code/datums/wounds/slash.dm
+++ b/code/datums/wounds/slash.dm
@@ -55,7 +55,7 @@
old_wound.clear_highest_scar()
else
set_blood_flow(initial_flow)
- if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY)
+ if(limb.can_bleed() && attack_direction)
victim.spray_blood(attack_direction, severity)
if(!highest_scar)
@@ -105,8 +105,10 @@
if (!victim) // if we are dismembered, we can still take damage, its fine to check here
return
- if(victim.stat != DEAD && wound_bonus != CANT_WOUND && wounding_type == WOUND_SLASH) // can't stab dead bodies to make it bleed faster this way
- adjust_blood_flow(WOUND_SLASH_DAMAGE_FLOW_COEFF * wounding_dmg)
+ // can't stab dead bodies to make it bleed faster this way
+ if(victim.stat == DEAD || wound_bonus == CANT_WOUND || (wounding_type != WOUND_SLASH))
+ return
+ adjust_blood_flow(WOUND_SLASH_DAMAGE_FLOW_COEFF * wounding_dmg)
return ..()
@@ -114,7 +116,6 @@
// say we have 3 severe cuts with 3 blood flow each, pretty reasonable
// compare with being at 100 brute damage before, where you bled (brute/100 * 2), = 2 blood per tile
var/bleed_amt = min(blood_flow * 0.1, 1) // 3 * 3 * 0.1 = 0.9 blood total, less than before! the share here is .3 blood of course.
-
if(limb.current_gauze) // gauze stops all bleeding from dragging on this limb, but wears the gauze out quicker
limb.seep_gauze(bleed_amt * 0.33)
return
@@ -189,7 +190,9 @@
return suture(I, user)
/datum/wound/slash/flesh/try_handling(mob/living/carbon/human/user)
- if(user.pulling != victim || user.zone_selected != limb.body_zone || !isfelinid(user) || !victim.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE))
+ if(user.pulling != victim || user.zone_selected != limb.body_zone || !victim.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE))
+ return FALSE
+ if(!istype(user.getorganslot(ORGAN_SLOT_TONGUE), /obj/item/organ/internal/tongue/cat))
return FALSE
if(DOING_INTERACTION_WITH_TARGET(user, victim))
to_chat(user, span_warning("You're already interacting with [victim]!"))
@@ -197,9 +200,6 @@
if(user.is_mouth_covered())
to_chat(user, span_warning("Your mouth is covered, you can't lick [victim]'s wounds!"))
return
- if(!user.getorganslot(ORGAN_SLOT_TONGUE))
- to_chat(user, span_warning("You can't lick wounds without a tongue!")) // f in chat
- return
lick_wounds(user)
return TRUE
@@ -207,8 +207,7 @@
/// if a felinid is licking this cut to reduce bleeding
/datum/wound/slash/flesh/proc/lick_wounds(mob/living/carbon/human/user)
// transmission is one way patient -> felinid since google said cat saliva is antiseptic or whatever, and also because felinids are already risking getting beaten for this even without people suspecting they're spreading a deathvirus
- for(var/i in victim.diseases)
- var/datum/disease/iter_disease = i
+ for(var/datum/disease/iter_disease as anything in victim.diseases)
if(iter_disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
user.ForceContractDisease(iter_disease)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index f9d7071f74b..539edf089a0 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -932,8 +932,11 @@
* Default behaviour is to move back from the item that hit us
*/
/atom/proc/hitby_react(atom/movable/harmed_atom)
- if(harmed_atom && isturf(harmed_atom.loc))
+ if(QDELETED(harmed_atom))
+ return FALSE
+ if(isturf(harmed_atom.loc))
step(harmed_atom, turn(harmed_atom.dir, 180))
+ return TRUE
///Handle the atom being slipped over
/atom/proc/handle_slip(mob/living/carbon/slipped_carbon, knockdown_amount, obj/slipping_object, lube, paralyze, force_drop)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index cad13f469fc..d2f47dd7b18 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1121,7 +1121,7 @@
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
///If this returns FALSE then callback will not be called.
-/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE)
+/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = FALSE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE)
. = FALSE
if(QDELETED(src))
@@ -1270,32 +1270,51 @@
return
-/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect)
+/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
if(!no_effect && (visual_effect_icon || used_item))
do_item_attack_animation(attacked_atom, visual_effect_icon, used_item)
if(attacked_atom == src)
return //don't do an animation if attacking self
+
var/pixel_x_diff = 0
var/pixel_y_diff = 0
- var/turn_dir = 1
-
- var/direction = get_dir(src, attacked_atom)
- if(direction & NORTH)
- pixel_y_diff = 8
- turn_dir = prob(50) ? -1 : 1
- else if(direction & SOUTH)
- pixel_y_diff = -8
- turn_dir = prob(50) ? -1 : 1
-
- if(direction & EAST)
- pixel_x_diff = 8
- else if(direction & WEST)
- pixel_x_diff = -8
- turn_dir = -1
+ var/turn_angle = 0
+ if(angled)
+ var/angle = get_angle(src, attacked_atom)
+ pixel_x_diff = round(sin(angle) * (world.icon_size/4))
+ pixel_y_diff = round(cos(angle) * (world.icon_size/4))
+ if(angle <= 15)
+ turn_angle = angle
+ else if(angle <= 165)
+ turn_angle = 15
+ else if(angle <= 180)
+ turn_angle = 15 - (angle - 165)
+ else if(angle <= 195)
+ turn_angle = -(angle - 180)
+ else if(angle <= 345)
+ turn_angle = -15
+ else
+ turn_angle = angle-360
+ turn_angle = round(turn_angle)
+ else
+ var/direction = get_dir(src, attacked_atom)
+ if(direction & NORTH)
+ pixel_y_diff = world.icon_size/4
+ turn_angle = 0
+ else if(direction & SOUTH)
+ pixel_y_diff = -world.icon_size/4
+ turn_angle = 0
+
+ if(direction & EAST)
+ pixel_x_diff = world.icon_size/4
+ turn_angle = 15
+ else if(direction & WEST)
+ pixel_x_diff = -world.icon_size/4
+ turn_angle = -15
var/matrix/initial_transform = matrix(transform)
- var/matrix/rotated_transform = transform.Turn(15 * turn_dir)
+ var/matrix/rotated_transform = transform.Turn(turn_angle)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform=rotated_transform, time = 1, easing=BACK_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform=initial_transform, time = 2, easing=SINE_EASING, flags = ANIMATION_PARALLEL)
diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm
index 6fb9aa20188..3568686800d 100644
--- a/code/game/objects/effects/decals/cleanable.dm
+++ b/code/game/objects/effects/decals/cleanable.dm
@@ -24,11 +24,9 @@
if(decal_reagent)
reagents.add_reagent(decal_reagent, reagent_amount)
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))
- handle_merge_decal(C)
- return INITIALIZE_HINT_QDEL
+ for(var/obj/effect/decal/cleanable/cleanable in loc)
+ if(merge_decal(cleanable))
+ return INITIALIZE_HINT_QDEL
if(LAZYLEN(diseases))
var/list/datum/disease/diseases_to_add = list()
@@ -54,14 +52,23 @@
SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name)
return ..()
-/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)
+/// Attempts to merge with a given decal. Returns true if successful.
+/obj/effect/decal/cleanable/proc/merge_decal(obj/effect/decal/cleanable/merger)
+ if(replace_decal(merger))
+ handle_merge_decal(merger)
return TRUE
+ return FALSE
+
+/// Returns true if we should give up and qdel in favor of the pre-existing decal
+/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/merger)
+ if(mergeable_decal && merger != src && merger.type == type && !QDELETED(merger))
+ return TRUE
+ return FALSE
/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params)
if((istype(W, /obj/item/reagent_containers/cup) && !istype(W, /obj/item/reagent_containers/cup/rag)) || istype(W, /obj/item/reagent_containers/cup/glass))
if(src.reagents && W.reagents)
- . = 1 //so the containers don't splash their content on the src while scooping.
+ . = TRUE //so the containers don't splash their content on the src while scooping.
if(!src.reagents.total_volume)
to_chat(user, span_notice("[src] isn't thick enough to scoop up!"))
return
@@ -86,8 +93,7 @@
/obj/effect/decal/cleanable/fire_act(exposed_temperature, exposed_volume)
if(reagents)
reagents.expose_temperature(exposed_temperature)
- ..()
-
+ return ..()
//Add "bloodiness" of this blood's type, to the human's shoes
//This is on /cleanable because fuck this ancient mess
@@ -108,7 +114,7 @@
* Checks if this decal is a valid decal that can be blood crawled in.
*/
/obj/effect/decal/cleanable/proc/can_bloodcrawl_in()
- if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY))
+ if(blood_state in GLOB.bloody_blood_states)
return bloodiness
return FALSE
diff --git a/code/game/objects/effects/decals/cleanable/blood/hitsplatter.dm b/code/game/objects/effects/decals/cleanable/blood/hitsplatter.dm
new file mode 100644
index 00000000000..2245b716c6b
--- /dev/null
+++ b/code/game/objects/effects/decals/cleanable/blood/hitsplatter.dm
@@ -0,0 +1,132 @@
+/obj/effect/decal/cleanable/blood/hitsplatter
+ name = "blood splatter"
+ icon_state = "hitsplatter1"
+ random_icon_states = list("hitsplatter1", "hitsplatter2", "hitsplatter3")
+ pass_flags = PASSTABLE | PASSGRILLE
+ /// The turf we just came from, so we can back up when we hit a wall
+ var/turf/prev_loc
+ /// 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
+ /// Type of squirt decals we should try to create when moving
+ var/squirt_type = /obj/effect/decal/cleanable/blood/squirt
+
+/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/wounds/splatter.ogg', 60, TRUE, -1)
+ return ..()
+
+/// Set the splatter up to fly through the air until it rounds out of steam or hits something
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/fly_towards(turf/target_turf, range)
+ var/delay = 2
+ var/datum/move_loop/loop = SSmove_manager.move_towards(src, target_turf, delay, timeout = delay * range, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST)
+ RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
+ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
+ RegisterSignal(loop, COMSIG_PARENT_QDELETING, PROC_REF(loop_done))
+
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/pre_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ prev_loc = loc
+
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/post_move(datum/move_loop/source)
+ SIGNAL_HANDLER
+ var/list/blood_dna_info = GET_ATOM_BLOOD_DNA(src)
+ 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.is_eyes_covered())
+ splashed_human.adjust_blurriness(3)
+ to_chat(splashed_human, span_userdanger("You're blinded by a spray of blood!"))
+ if(splashed_human.glasses)
+ splashed_human.glasses.add_blood_DNA(blood_dna_info)
+ splashed_human.update_worn_glasses() //updates mob overlays to show the new blood (no refresh)
+ if(splashed_human.wear_mask)
+ splashed_human.wear_mask.add_blood_DNA(blood_dna_info)
+ splashed_human.update_worn_mask() //updates mob overlays to show the new blood (no refresh)
+ if(splashed_human.wear_suit)
+ splashed_human.wear_suit.add_blood_DNA(blood_dna_info)
+ splashed_human.update_worn_oversuit() //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_worn_undersuit() //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
+
+ if(squirt_type && isturf(loc))
+ var/obj/effect/decal/cleanable/squirt = locate(squirt_type) in loc
+ if(squirt)
+ squirt.add_blood_DNA(blood_dna_info)
+ else
+ squirt = new squirt_type(loc, get_dir(prev_loc, loc))
+ squirt.add_blood_DNA(blood_dna_info)
+ squirt.alpha = 0
+ animate(squirt, alpha = initial(squirt.alpha), time = 2)
+
+/obj/effect/decal/cleanable/blood/hitsplatter/proc/loop_done(datum/source)
+ SIGNAL_HANDLER
+ if(!QDELETED(src))
+ qdel(src)
+
+/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)
+ hit_endpoint = TRUE
+ qdel(src)
+ return
+
+ hit_endpoint = TRUE
+ if(istype(bumped_atom, /obj/structure/window))
+ //special window case
+ var/obj/structure/window/window = bumped_atom
+ window.become_bloodied(src)
+ skip = TRUE
+ else if(isturf(prev_loc))
+ abstract_move(bumped_atom)
+ skip = TRUE
+ //Adjust pixel offset to make splatters appear on the wall
+ var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new(prev_loc)
+ var/dir_to_wall = get_dir(src, bumped_atom)
+ final_splatter.pixel_x = (dir_to_wall & EAST ? world.icon_size : (dir_to_wall & WEST ? -world.icon_size : 0))
+ final_splatter.pixel_y = (dir_to_wall & NORTH ? world.icon_size : (dir_to_wall & SOUTH ? -world.icon_size : 0))
+ final_splatter.alpha = 0
+ animate(final_splatter, alpha = initial(final_splatter.alpha), time = 2)
+ 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
+ if(the_window.bloodied)
+ qdel(src)
+ 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)
diff --git a/code/game/objects/effects/decals/cleanable/blood/particle.dm b/code/game/objects/effects/decals/cleanable/blood/particle.dm
new file mode 100644
index 00000000000..1b1cb4b8631
--- /dev/null
+++ b/code/game/objects/effects/decals/cleanable/blood/particle.dm
@@ -0,0 +1,151 @@
+/// Blood drip subtype meant to be thrown around as a particle
+/obj/effect/decal/cleanable/blood/particle
+ name = "blood droplet"
+ icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires.
+ random_icon_states = list("drip1","drip2","drip3","drip4","drip5")
+ plane = GAME_PLANE
+ layer = BELOW_MOB_LAYER
+ should_dry = FALSE
+ bloodiness = BLOOD_AMOUNT_PER_DECAL * 0.1
+ mergeable_decal = FALSE
+ /// Splatter type we create when we bounce on the floor
+ var/obj/effect/decal/cleanable/splatter_type_floor = /obj/effect/decal/cleanable/blood/splatter/stacking
+ /// Splatter type we create when we bump on a wall
+ var/obj/effect/decal/cleanable/splatter_type_wall = /obj/effect/decal/cleanable/blood/splatter/over_window
+ /// Whether or not we transfer our pixel_x and pixel_y to the splatter, only works for floor splatters though
+ var/messy_splatter = TRUE
+
+/obj/effect/decal/cleanable/blood/particle/can_bloodcrawl_in()
+ return FALSE
+
+/obj/effect/decal/cleanable/blood/particle/proc/start_movement(movement_angle)
+ var/datum/component/movable_physics/movable_physics = GetComponent(/datum/component/movable_physics)
+ if(!movable_physics)
+ movable_physics = initialize_physics()
+ if(!isnull(movement_angle))
+ movable_physics.set_angle(movement_angle)
+
+/obj/effect/decal/cleanable/blood/particle/proc/initialize_physics()
+ return AddComponent(/datum/component/movable_physics, \
+ horizontal_velocity = rand(1 * 100, 5.5 * 100) * 0.01, \
+ vertical_velocity = rand(4 * 100, 4.5 * 100) * 0.01, \
+ horizontal_friction = rand(0.05 * 100, 0.1 * 100) * 0.01, \
+ vertical_friction = 10 * 0.05, \
+ z_floor = 0, \
+ bounce_callback = CALLBACK(src, PROC_REF(on_bounce)), \
+ bump_callback = CALLBACK(src, PROC_REF(on_bump)), \
+ )
+
+/obj/effect/decal/cleanable/blood/particle/proc/on_bounce()
+ if(!isturf(loc) || !splatter_type_floor)
+ qdel(src)
+ return
+ var/obj/effect/decal/cleanable/splatter
+ if(!ispath(splatter_type_floor, /obj/effect/decal/cleanable/blood/splatter/stacking))
+ splatter = new splatter_type_floor(loc)
+ if(messy_splatter)
+ splatter.pixel_x = src.pixel_x
+ splatter.pixel_y = src.pixel_y
+ else
+ var/obj/effect/decal/cleanable/blood/splatter/stacking/stacker = locate(splatter_type_floor) in loc
+ if(!stacker)
+ stacker = new splatter_type_floor(loc)
+ if(messy_splatter)
+ var/mutable_appearance/existing_appearance = stacker.splat_overlays[1]
+ existing_appearance.pixel_x = src.pixel_x
+ existing_appearance.pixel_y = src.pixel_y
+ stacker.bloodiness = src.bloodiness
+ stacker.update_appearance(UPDATE_ICON)
+ stacker.alpha = 0
+ animate(stacker, alpha = 255, time = 2)
+ else
+ var/obj/effect/decal/cleanable/blood/splatter/stacking/other_splatter = new splatter_type_floor()
+ if(messy_splatter)
+ var/mutable_appearance/existing_appearance = other_splatter.splat_overlays[1]
+ existing_appearance.pixel_x = src.pixel_x
+ existing_appearance.pixel_y = src.pixel_y
+ other_splatter.forceMove(loc)
+ other_splatter.bloodiness = src.bloodiness
+ other_splatter.update_appearance(UPDATE_ICON)
+ other_splatter.alpha = 0
+ animate(other_splatter, alpha = stacker.alpha, time = 2)
+ animate(other_splatter, color = stacker.color, time = 2)
+ addtimer(CALLBACK(other_splatter, TYPE_PROC_REF(/obj/effect/decal/cleanable/blood/splatter/stacking, delayed_merge), stacker), 2)
+ splatter = stacker
+ var/list/our_blood_dna = GET_ATOM_BLOOD_DNA(src)
+ if(our_blood_dna)
+ splatter.add_blood_DNA(our_blood_dna)
+ qdel(src)
+
+/obj/effect/decal/cleanable/blood/particle/proc/on_bump(atom/bumped_atom)
+ if(!isturf(loc) || !splatter_type_wall)
+ return
+ if(iswallturf(bumped_atom))
+ //Adjust pixel offset to make splatters appear on the wall
+ var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new splatter_type_wall(loc)
+ var/dir_to_wall = get_dir(src, bumped_atom)
+ final_splatter.pixel_x = (dir_to_wall & EAST ? world.icon_size : (dir_to_wall & WEST ? -world.icon_size : 0))
+ final_splatter.pixel_y = (dir_to_wall & NORTH ? world.icon_size : (dir_to_wall & SOUTH ? -world.icon_size : 0))
+ final_splatter.alpha = 0
+ animate(final_splatter, alpha = initial(final_splatter.alpha), time = 2)
+ var/list/blood_dna = GET_ATOM_BLOOD_DNA(src)
+ if(blood_dna)
+ final_splatter.add_blood_DNA(blood_dna)
+ else if(istype(bumped_atom, /obj/structure/window))
+ var/obj/structure/window/the_window = bumped_atom
+ the_window.become_bloodied(src)
+ qdel(src)
+
+/// subtype of splatter capable of doing proper "stacking" behavior
+/obj/effect/decal/cleanable/blood/splatter/stacking
+ /// Maximum amount of blood overlays we can have visually
+ var/maximum_splats = 50
+ /// Listing containing overlays of all the splatters we've merged with
+ var/list/splat_overlays = list()
+
+/obj/effect/decal/cleanable/blood/splatter/stacking/Initialize(mapload)
+ . = ..()
+ var/mutable_appearance/our_appearance = mutable_appearance(src.icon, src.icon_state)
+ our_appearance.alpha = src.alpha
+ our_appearance.color = src.color
+ our_appearance.pixel_x = src.pixel_x
+ our_appearance.pixel_y = src.pixel_y
+ icon_state = null
+ color = null
+ alpha = 255
+ pixel_x = 0
+ pixel_y = 0
+ splat_overlays += our_appearance
+ update_appearance(UPDATE_ICON)
+
+/obj/effect/decal/cleanable/blood/splatter/stacking/Destroy()
+ . = ..()
+ splat_overlays = null
+
+/obj/effect/decal/cleanable/blood/splatter/stacking/update_overlays()
+ . = ..()
+ var/splat_length = length(splat_overlays)
+ if(splat_length > maximum_splats)
+ splat_overlays = splat_overlays.Splice(splat_length - maximum_splats, splat_length)
+ . += splat_overlays
+
+/obj/effect/decal/cleanable/blood/splatter/stacking/handle_merge_decal(obj/effect/decal/cleanable/merger)
+ . = ..()
+ if(istype(merger, /obj/effect/decal/cleanable/blood/splatter/stacking))
+ var/obj/effect/decal/cleanable/blood/splatter/stacking/stacker = merger
+ stacker.splat_overlays |= splat_overlays
+ stacker.get_timer() //reset drying time, ripbozo
+ stacker.update_appearance(UPDATE_ICON)
+
+/// Called so that a spawning animation can be performed by blood particles, after that is done we merge with merger
+/obj/effect/decal/cleanable/blood/splatter/stacking/proc/delayed_merge(obj/effect/decal/cleanable/blood/splatter/stacking/merger)
+ if(QDELETED(merger))
+ if(!QDELETED(src))
+ qdel(src)
+ return
+
+ if(QDELETED(src))
+ return
+
+ if(merge_decal(merger))
+ qdel(src)
diff --git a/code/game/objects/effects/decals/cleanable/blood/squirt.dm b/code/game/objects/effects/decals/cleanable/blood/squirt.dm
new file mode 100644
index 00000000000..716130f200b
--- /dev/null
+++ b/code/game/objects/effects/decals/cleanable/blood/squirt.dm
@@ -0,0 +1,21 @@
+/obj/effect/decal/cleanable/blood/squirt
+ name = "blood squirt"
+ desc = "Raining blood, from a lacerated sky, bleeding its horror!"
+ icon_state = "squirt"
+ random_icon_states = null
+ dryname = "dried blood squirt"
+ drydesc = "Creating my structure - Now I shall reign in blood!"
+
+/obj/effect/decal/cleanable/blood/squirt/Initialize(mapload, direction)
+ if(!isnull(direction))
+ //has to be done before we call replace_decal()
+ setDir(direction)
+ return ..()
+
+/obj/effect/decal/cleanable/blood/squirt/replace_decal(obj/effect/decal/cleanable/merger)
+ . = ..()
+ if(!.)
+ return
+ //squirts of the same dir are redundant, but not if they're different
+ if(merger.dir != src.dir)
+ return FALSE
diff --git a/code/game/objects/effects/decals/cleanable/blood/trail.dm b/code/game/objects/effects/decals/cleanable/blood/trail.dm
new file mode 100644
index 00000000000..d70421781fd
--- /dev/null
+++ b/code/game/objects/effects/decals/cleanable/blood/trail.dm
@@ -0,0 +1,50 @@
+/obj/effect/decal/cleanable/blood/trail
+ name = "blood trail"
+ desc = "Your instincts say you shouldn't be following these."
+ icon = 'icons/effects/blood.dmi'
+ icon_state = null
+ random_icon_states = null
+ beauty = -50
+ bloodiness = BLOOD_AMOUNT_PER_DECAL * 0.2
+ dryname = "dried blood trail"
+ drydesc = "It's probably safer now, but your instincts still say you shouldn't be following these."
+ /// Existing directions for building our overlays
+ var/list/existing_dirs = list()
+ /// Last direction we added to existing dirs
+ var/last_dir = NONE
+
+/obj/effect/decal/cleanable/blood/trail/proc/add_trail(state = "ltrail", direction)
+ var/new_dir = direction
+ if((new_dir != last_dir) && (last_dir in GLOB.cardinals) && (direction in GLOB.cardinals))
+ switch(last_dir)
+ if(NORTH)
+ if(direction & EAST)
+ new_dir = NORTHWEST
+ else
+ new_dir = NORTHEAST
+ if(SOUTH)
+ if(direction & EAST)
+ new_dir = SOUTHWEST
+ else
+ new_dir = SOUTHEAST
+ if(EAST)
+ if(direction & NORTH)
+ new_dir = SOUTHEAST
+ else
+ new_dir = NORTHEAST
+ if(WEST)
+ if(direction & NORTH)
+ new_dir = SOUTHWEST
+ else
+ new_dir = NORTHWEST
+ existing_dirs -= "[last_dir]"
+ if(existing_dirs["[new_dir]"])
+ return
+ existing_dirs["[new_dir]"] = image(icon, state, dir = new_dir)
+ last_dir = new_dir
+ update_appearance(UPDATE_ICON)
+
+/obj/effect/decal/cleanable/blood/trail/update_overlays()
+ . = ..()
+ for(var/direction in existing_dirs)
+ . += existing_dirs[direction]
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 6d0b8a5c311..d66b256e818 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -1,3 +1,6 @@
+/// List of blood states that are well, actually blood
+GLOBAL_LIST_INIT(bloody_blood_states, list(BLOOD_STATE_HUMAN, BLOOD_STATE_XENO))
+
/obj/effect/decal/cleanable/blood
name = "blood"
desc = "It's red and gooey. Perhaps it's the chef's cooking?"
@@ -54,11 +57,13 @@
qdel(GetComponent(smell_type))
return TRUE
-/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C)
- C.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
- if (bloodiness)
- C.bloodiness = min((C.bloodiness + bloodiness), BLOOD_AMOUNT_PER_DECAL)
- return ..()
+/obj/effect/decal/cleanable/blood/handle_merge_decal(obj/effect/decal/cleanable/merger)
+ . = ..()
+ var/list/blood_dna = GET_ATOM_BLOOD_DNA(src)
+ if(LAZYLEN(blood_dna))
+ merger.add_blood_DNA(blood_dna)
+ if(bloodiness)
+ merger.bloodiness = min((merger.bloodiness + bloodiness), BLOOD_AMOUNT_PER_DECAL)
/obj/effect/decal/cleanable/blood/old
bloodiness = 0
@@ -90,8 +95,8 @@
absorb_squirts = FALSE
/obj/effect/decal/cleanable/blood/tracks
- icon_state = "tracks"
desc = "They look like tracks left by wheels."
+ icon_state = "tracks"
random_icon_states = null
beauty = -50
dryname = "dried tracks"
@@ -227,14 +232,17 @@
icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires.
random_icon_states = list("drip1","drip2","drip3","drip4","drip5")
bloodiness = 0
- var/drips = 1
dryname = "drips of blood"
drydesc = "It's red."
+ /// Amount of blood droplets we currently have
+ var/drips = 1
smell_type = /datum/component/smell/subtle
/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in()
- return TRUE
+ if(blood_state in GLOB.bloody_blood_states)
+ return TRUE
+ return FALSE
//BLOODY FOOTPRINTS
/obj/effect/decal/cleanable/blood/footprints
@@ -335,123 +343,10 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
return ..()
/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in()
- if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY))
+ if(blood_state in GLOB.bloody_blood_states)
return TRUE
- return FALSE
-/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
- smell_type = /datum/component/smell/subtle
-
-/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/wounds/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
-/obj/effect/decal/cleanable/blood/hitsplatter/proc/fly_towards(turf/target_turf, range)
- var/delay = 2
- var/datum/move_loop/loop = SSmove_manager.move_towards(src, target_turf, delay, timeout = delay * range, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST)
- RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
- RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
- RegisterSignal(loop, COMSIG_PARENT_QDELETING, PROC_REF(loop_done))
-
-/obj/effect/decal/cleanable/blood/hitsplatter/proc/pre_move(datum/move_loop/source)
- SIGNAL_HANDLER
- prev_loc = loc
-
-/obj/effect/decal/cleanable/blood/hitsplatter/proc/post_move(datum/move_loop/source)
- SIGNAL_HANDLER
- var/turf/T = get_turf(src)
-
- for(var/atom/movable/iter_atom as anything in T)
- 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.is_eyes_covered())
- splashed_human.blur_eyes(3)
- to_chat(splashed_human, span_alert("You're blinded by a spray of blood!"))
- if(splashed_human.wear_suit)
- splashed_human.wear_suit.add_blood_DNA(blood_dna_info)
- splashed_human.update_worn_oversuit() //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_worn_undersuit() //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
-
- new /obj/effect/decal/cleanable/blood/squirt(T, get_dir(prev_loc, loc), blood_dna_info)
-
-/obj/effect/decal/cleanable/blood/hitsplatter/proc/loop_done(datum/source)
- SIGNAL_HANDLER
- if(!QDELETED(src))
- qdel(src)
-
-/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)
+ return FALSE
/obj/effect/decal/cleanable/blood/squirt
name = "blood trail"
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index d47d167e501..172495ae569 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -811,7 +811,7 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE)
return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum)
-/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
+/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
if(HAS_TRAIT(src, TRAIT_NODROP))
return
thrownby = WEAKREF(thrower)
@@ -926,8 +926,8 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC)
ash_type = /obj/effect/decal/cleanable/ash/large
var/obj/effect/decal/cleanable/ash/A = new ash_type(T)
- A.desc += "\nLooks like this used to be \an [name] some time ago."
- ..()
+ A.desc += "\nLooks like this used to be \a [src] some time ago."
+ return ..()
/obj/item/acid_melt()
if(!QDELETED(src))
@@ -935,8 +935,8 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
var/obj/effect/decal/cleanable/molten_object/MO = new(T)
MO.pixel_x = rand(-16,16)
MO.pixel_y = rand(-16,16)
- MO.desc = "Looks like this was \an [src] some time ago."
- ..()
+ MO.desc = "Looks like this was \a [src] some time ago."
+ return ..()
/obj/item/proc/microwave_act(obj/machinery/microwave/M)
if(SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M) & COMPONENT_SUCCESFUL_MICROWAVE)
diff --git a/code/game/objects/items/devices/table_clock.dm b/code/game/objects/items/devices/table_clock.dm
index 336214151ec..5f161ff146b 100644
--- a/code/game/objects/items/devices/table_clock.dm
+++ b/code/game/objects/items/devices/table_clock.dm
@@ -43,7 +43,7 @@
span_notice("You hear repeated smashing!"),
)
-/obj/item/table_clock/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback, force, gentle, quickstart)
+/obj/item/table_clock/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(!.)
return
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index 3e5b0bf470a..da29e3fca92 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -455,7 +455,7 @@
///Amount of time to knock the target down for once it's hit in deciseconds.
var/knockdown = 0
-/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle = FALSE, quickstart = TRUE)
+/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
if(!..())
return
playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, TRUE)
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 81cf3dd75ee..aea44b005a1 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -1,6 +1,6 @@
/obj/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- ..()
+ . = ..()
take_damage(AM.throwforce, BRUTE, MELEE, 1, get_dir(src, AM))
/obj/ex_act(severity, target)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index b9bc8669439..8938bca36d3 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -384,6 +384,28 @@
. = ..()
+/obj/structure/window/proc/become_bloodied(obj/effect/decal/cleanable/blood/splatter)
+ if(bloodied || !fulltile || !splatter)
+ return
+ var/obj/effect/decal/cleanable/blood/splatter/over_window/mess = new()
+ mess.forceMove(src)
+ vis_contents += mess
+ mess.alpha = 0
+ animate(mess, alpha = initial(mess.alpha), time = 2)
+ bloodied = TRUE
+
+/obj/structure/window/wash(clean_types)
+ . = ..()
+ if(!(clean_types & CLEAN_SCRUB))
+ return
+ set_opacity(initial(opacity))
+ remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
+ for(var/atom/movable/cleanable as anything in src)
+ if(!cleanable.wash(clean_types))
+ continue
+ vis_contents -= cleanable
+ bloodied = FALSE
+
/obj/structure/window/Move()
. = ..()
if(. && isturf(loc))
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index cf022ad3d09..594937e0111 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -761,8 +761,6 @@
temp += max((B.bloodiness**2)/800,1)
new /obj/effect/temp_visual/cult/turf/floor(get_turf(B))
qdel(B)
- for(var/obj/effect/decal/cleanable/trail_holder/TH in view(T, 2))
- qdel(TH)
if(temp)
user.Beam(T,icon_state="drainbeam", time = 15)
new /obj/effect/temp_visual/cult/sparks(get_turf(user))
diff --git a/code/modules/antagonists/traitor/objectives/assassination.dm b/code/modules/antagonists/traitor/objectives/assassination.dm
index 28741759571..6e4062d8f39 100644
--- a/code/modules/antagonists/traitor/objectives/assassination.dm
+++ b/code/modules/antagonists/traitor/objectives/assassination.dm
@@ -150,7 +150,7 @@
taker.visible_message(span_notice("[taker] holds [behead_goal] into the air for a moment."), span_boldnotice("You lift [behead_goal] into the air for a moment."))
succeed_objective()
-/datum/traitor_objective/assassinate/behead/proc/on_target_dismembered(datum/source, obj/item/bodypart/head/lost_head, special)
+/datum/traitor_objective/assassinate/behead/proc/on_target_dismembered(datum/source, obj/item/bodypart/head/lost_head, special, dismembered)
SIGNAL_HANDLER
if(!istype(lost_head))
return
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 9cce8adc370..e70e5644998 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -133,7 +133,7 @@
. = ..()
setDir(t)
-/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
+/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
olddir = dir
diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm b/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm
index f3d6c14160e..818ab2158d4 100644
--- a/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm
+++ b/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm
@@ -17,7 +17,7 @@
. = ..()
. += span_notice("Throw this at objects or creatures to freeze them, it will boomerang back so be cautious!")
-/obj/item/freeze_cube/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, quickstart = TRUE)
+/obj/item/freeze_cube/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
if(!.)
return
diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm
index d6dc450f08b..6229c050678 100644
--- a/code/modules/mob/living/basic/basic_defense.dm
+++ b/code/modules/mob/living/basic/basic_defense.dm
@@ -171,7 +171,7 @@
adjustBruteLoss(20)
return
-/mob/living/basic/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect)
+/mob/living/basic/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
if(!no_effect && !visual_effect_icon && melee_damage_upper)
if(attack_vis_effect && !iswallturf(A)) // override the standard visual effect.
visual_effect_icon = attack_vis_effect
@@ -179,7 +179,7 @@
visual_effect_icon = ATTACK_EFFECT_PUNCH
else
visual_effect_icon = ATTACK_EFFECT_SMASH
- ..()
+ return ..()
/mob/living/basic/update_stat()
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 6de5cfcb633..414efc2f55e 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -1,16 +1,13 @@
-#define BLOOD_DRIP_RATE_MOD 90 //Greater number means creating blood drips more often while bleeding
-
/****************************************************
BLOOD SYSTEM
****************************************************/
// Takes care blood loss and regeneration
/mob/living/carbon/human/handle_blood(delta_time, times_fired)
-
if(HAS_TRAIT(src, TRAIT_NOBLOOD) || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
return
- if(bodytemperature < BLOOD_STOP_TEMP || (HAS_TRAIT(src, TRAIT_HUSK))) //cold or husked people do not pump the blood.
+ if(bodytemperature < BLOOD_STOP_TEMP || HAS_TRAIT(src, TRAIT_HUSK)) //cold or husked people do not pump blood.
return
//Blood regeneration if there is some space
@@ -65,9 +62,8 @@
for(var/obj/item/bodypart/iter_part as anything in bodyparts)
var/iter_bleed_rate = iter_part.get_modified_bleed_rate()
temp_bleed += iter_bleed_rate * delta_time
-
if(iter_part.generic_bleedstacks) // If you don't have any bleedstacks, don't try and heal them
- iter_part.adjustBleedStacks(-1, 0)
+ iter_part.adjustBleedStacks(-1, minimum = 0)
if(temp_bleed)
bleed(temp_bleed)
@@ -79,35 +75,30 @@
iter_part.update_part_wound_overlay()
//Makes a blood drop, leaking amt units of blood from the mob
-/mob/living/carbon/proc/bleed(amt)
- if(!blood_volume)
+/mob/living/carbon/proc/bleed(amt, no_visual = FALSE)
+ if(!blood_volume || HAS_TRAIT(src, TRAIT_NOBLOOD))
return
blood_volume = max(blood_volume - amt, 0)
//Blood loss still happens in locker, floor stays clean
- if(isturf(loc) && prob(sqrt(amt)*BLOOD_DRIP_RATE_MOD))
- add_splatter_floor(loc, (amt >= 10))
+ if(!no_visual && isturf(loc) && prob(sqrt(amt) * 80))
+ add_splatter_floor(loc, small_drip = (amt < 10))
-/mob/living/carbon/human/bleed(amt)
+/mob/living/carbon/human/bleed(amt, no_visual = FALSE)
amt *= physiology.bleed_mod
- if(!HAS_TRAIT(src, TRAIT_NOBLOOD))
- ..()
+ return ..()
/// A helper to see how much blood we're losing per tick
/mob/living/carbon/proc/get_bleed_rate()
- if(!blood_volume)
- return
+ if(!blood_volume || HAS_TRAIT(src, TRAIT_NOBLOOD))
+ return 0
var/bleed_amt = 0
- for(var/X in bodyparts)
- var/obj/item/bodypart/iter_bodypart = X
+ for(var/obj/item/bodypart/iter_bodypart as anything in bodyparts)
bleed_amt += iter_bodypart.get_modified_bleed_rate()
return bleed_amt
/mob/living/carbon/human/get_bleed_rate()
- if(HAS_TRAIT(src, TRAIT_NOBLOOD))
- return
- . = ..()
- . *= physiology.bleed_mod
+ return ..() * physiology.bleed_mod
/**
* bleed_warn() is used to for carbons with an active client to occasionally receive messages warning them about their bleeding status (if applicable)
@@ -117,7 +108,7 @@
* * forced-
*/
/mob/living/carbon/proc/bleed_warn(bleed_amt = 0, forced = FALSE)
- if(!blood_volume || !client)
+ if(!client || !blood_volume || HAS_TRAIT(src, TRAIT_NOBLOOD))
return
if(!COOLDOWN_FINISHED(src, bleeding_message_cd) && !forced)
return
@@ -151,8 +142,7 @@
rate_of_change = ", but it's clotting up quickly!"
else
// flick through our wounds to see if there are any bleeding ones getting worse or holding flow (maybe move this to handle_blood and cache it so we don't need to cycle through the wounds so much)
- for(var/i in all_wounds)
- var/datum/wound/iter_wound = i
+ for(var/datum/wound/iter_wound as anything in all_wounds)
if(!iter_wound.blood_flow)
continue
var/iter_wound_roc = iter_wound.get_bleed_rate_of_change()
@@ -168,18 +158,13 @@
to_chat(src, span_warning("[bleeding_severity][rate_of_change]"))
COOLDOWN_START(src, bleeding_message_cd, next_cooldown)
-/mob/living/carbon/human/bleed_warn(bleed_amt = 0, forced = FALSE)
- if(!HAS_TRAIT(src, TRAIT_NOBLOOD))
- return ..()
-
/mob/living/proc/restore_blood()
blood_volume = initial(blood_volume)
/mob/living/carbon/restore_blood()
blood_volume = BLOOD_VOLUME_NORMAL
- for(var/i in bodyparts)
- var/obj/item/bodypart/BP = i
- BP.setBleedStacks(0)
+ for(var/obj/item/bodypart/bleed_part as anything in bodyparts)
+ bleed_part.setBleedStacks(0)
/****************************************************
BLOOD TRANSFERS
@@ -271,25 +256,23 @@
return
/mob/living/simple_animal/get_blood_id()
- if(blood_volume)
- return /datum/reagent/blood
+ if(HAS_TRAIT(src, TRAIT_NOBLOOD))
+ return
+ return /datum/reagent/blood
/mob/living/carbon/human/get_blood_id()
- if(HAS_TRAIT(src, TRAIT_HUSK))
+ if(HAS_TRAIT(src, TRAIT_HUSK) || HAS_TRAIT(src, TRAIT_NOBLOOD))
return
if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS] && is_clown_job(mind?.assigned_role))
return /datum/reagent/colorful_reagent
if(dna.species.exotic_blood)
return dna.species.exotic_blood
- else if(HAS_TRAIT(src, TRAIT_NOBLOOD))
- return
return /datum/reagent/blood
// This is has more potential uses, and is probably faster than the old proc.
/proc/get_safe_blood(bloodtype)
- . = list()
if(!bloodtype)
- return
+ return list()
var/static/list/bloodtypes_safe = list(
"A-" = list("A-", "O-"),
@@ -304,21 +287,21 @@
"U" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+", "L", "U")
)
- var/safe = bloodtypes_safe[bloodtype]
- if(safe)
- . = safe
+ return bloodtypes_safe[bloodtype]
//to add a splatter of blood or other mob liquid.
-/mob/living/proc/add_splatter_floor(turf/T, small_drip)
- if(get_blood_id() != /datum/reagent/blood)
+/mob/living/proc/add_splatter_floor(turf/splattered, small_drip)
+ if((get_blood_id() != /datum/reagent/blood) || HAS_TRAIT(src, TRAIT_NOBLOOD))
+ return
+ if(!splattered)
+ splattered = get_turf(src)
+ if(isclosedturf(splattered) || (isgroundlessturf(splattered) && !SSmapping.get_turf_below(splattered)))
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
+ var/obj/effect/decal/cleanable/blood/drip/drop = locate() in splattered
if(drop)
if(drop.drips < 5)
drop.drips++
@@ -329,36 +312,61 @@
temp_blood_DNA = GET_ATOM_BLOOD_DNA(drop) //we transfer the dna from the drip to the splatter
qdel(drop)//the drip is replaced by a bigger splatter
else
- drop = new(T, get_static_viruses())
+ drop = new(splattered, get_static_viruses())
drop.transfer_mob_blood_dna(src)
return
// Find a blood decal or create a new one.
- var/obj/effect/decal/cleanable/blood/B = locate() in T
- if(!B)
- B = new /obj/effect/decal/cleanable/blood/splatter(T, get_static_viruses())
- if(QDELETED(B)) //Give it up
+ var/obj/effect/decal/cleanable/blood/decal = locate() in splattered
+ if(!decal)
+ decal = new /obj/effect/decal/cleanable/blood/splatter(splattered, get_static_viruses())
+ if(QDELETED(decal)) //Give it up
return
- B.bloodiness = min((B.bloodiness + BLOOD_AMOUNT_PER_DECAL), BLOOD_POOL_MAX)
- B.transfer_mob_blood_dna(src) //give blood info to the blood decal.
+ decal.bloodiness = min((decal.bloodiness + BLOOD_AMOUNT_PER_DECAL), BLOOD_POOL_MAX)
+ splattered.transfer_mob_blood_dna(src) //give blood info to the blood decal.
if(temp_blood_DNA)
- B.add_blood_DNA(temp_blood_DNA)
-
-/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip)
- if(!HAS_TRAIT(src, TRAIT_NOBLOOD))
- ..()
-
-/mob/living/carbon/alien/add_splatter_floor(turf/T, small_drip)
- if(!T)
- T = get_turf(src)
- var/obj/effect/decal/cleanable/xenoblood/B = locate() in T.contents
- if(!B)
- B = new(T)
- B.add_blood_DNA(list("UNKNOWN DNA" = "X*"))
-
-/mob/living/silicon/robot/add_splatter_floor(turf/T, small_drip)
- if(!T)
- T = get_turf(src)
- var/obj/effect/decal/cleanable/oil/B = locate() in T.contents
- if(!B)
- B = new(T)
+ splattered.add_blood_DNA(temp_blood_DNA)
+
+/mob/living/carbon/alien/add_splatter_floor(turf/splattered, small_drip)
+ if(!splattered)
+ splattered = get_turf(src)
+ var/obj/effect/decal/cleanable/xenoblood/decal = locate() in splattered
+ if(!decal)
+ decal = new(splattered)
+ decal.add_blood_DNA(list("UNKNOWN DNA" = "X*"))
+
+/mob/living/silicon/robot/add_splatter_floor(turf/splattered, small_drip)
+ if(!splattered)
+ splattered = get_turf(src)
+ var/obj/effect/decal/cleanable/oil/decal = locate() in splattered
+ if(!decal)
+ decal = new(splattered)
+
+/**
+ * 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/proc/spray_blood(splatter_direction, splatter_strength = 3)
+ if(!isturf(loc) || !blood_volume || HAS_TRAIT(src, TRAIT_NOBLOOD))
+ return
+ var/obj/effect/decal/cleanable/blood/hitsplatter/our_splatter = new(loc)
+ our_splatter.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
+ var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
+ our_splatter.fly_towards(targ, splatter_strength)
+
+/**
+ * Helper proc for throwing blood particles around, similar to the spray_blood proc.
+ */
+/mob/living/proc/blood_particles(amount = rand(0, 2), angle = rand(0,360), min_deviation = -30, max_deviation = 30, min_pixel_z = 0, max_pixel_z = 6)
+ if(!isturf(loc) || !blood_volume ||HAS_TRAIT(src, TRAIT_NOBLOOD))
+ return
+ for(var/i in 1 to amount)
+ var/obj/effect/decal/cleanable/blood/particle/droplet = new(loc)
+ droplet.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
+ droplet.pixel_z = rand(min_pixel_z, max_pixel_z)
+ droplet.start_movement(angle + rand(min_deviation, max_deviation))
diff --git a/code/modules/mob/living/carbon/alien/adult/adult_defense.dm b/code/modules/mob/living/carbon/alien/adult/adult_defense.dm
index 9edb0319aab..bb9bef0f32a 100644
--- a/code/modules/mob/living/carbon/alien/adult/adult_defense.dm
+++ b/code/modules/mob/living/carbon/alien/adult/adult_defense.dm
@@ -48,7 +48,7 @@
to_chat(user, span_warning("Your punch misses [src]!"))
-/mob/living/carbon/alien/adult/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
+/mob/living/carbon/alien/adult/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
if(!no_effect && !visual_effect_icon)
visual_effect_icon = ATTACK_EFFECT_CLAW
- ..()
+ return ..()
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index f35c2f6bc1b..f3606c8e9f2 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -79,9 +79,9 @@
/mob/living/carbon/alien/getTrail()
if(getBruteLoss() < 200)
- return pick (list("xltrails_1", "xltrails2"))
- else
- return pick (list("xttrails_1", "xttrails2"))
+ return pick("xltrails_1", "xltrails2")
+ return pick("xttrails_1", "xttrails2")
+
/*----------------------------------------
Proc: AddInfectionImages()
Des: Gives the client of the alien an image on each infected mob.
diff --git a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
index 3fa78271d6f..525fc77f0f9 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm
@@ -26,7 +26,7 @@
return
user.AddComponent(/datum/component/force_move, get_step_away(user,src, 30))
-/mob/living/carbon/alien/larva/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
+/mob/living/carbon/alien/larva/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
if(!no_effect && !visual_effect_icon)
visual_effect_icon = ATTACK_EFFECT_BITE
- ..()
+ return ..()
diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm
index 71dabff7811..fa6208c731e 100644
--- a/code/modules/mob/living/carbon/alien/special/facehugger.dm
+++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm
@@ -103,7 +103,7 @@
if(CanHug(AM) && Adjacent(AM))
return Leap(AM)
-/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, quickstart = TRUE)
+/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
if(!.)
return
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 619c8fd6661..5b69cf4fb9b 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -195,7 +195,7 @@
power_throw++
if(neckgrab_throw)
power_throw++
- do_attack_animation(target, no_effect = TRUE)
+ do_attack_animation(target, no_effect = TRUE, angled = TRUE)
playsound(loc, 'sound/weapons/punchmiss.ogg', 50, TRUE, -1)
visible_message(span_danger("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]"), \
span_danger("You throw [thrown_thing][power_throw ? " really hard!" : "."]"))
@@ -1362,25 +1362,6 @@
log_combat(shover, target, "shoved", addition = "into [name]")
return COMSIG_CARBON_SHOVE_HANDLED
-/**
- * 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.add_blood_DNA(GET_ATOM_BLOOD_DNA(src))
- our_splatter.blood_dna_info = get_blood_dna_list()
- var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
- our_splatter.fly_towards(targ, splatter_strength)
-
-
/mob/living/carbon/proc/KnockToFloor(silent = TRUE, ignore_canknockdown = FALSE, knockdown_amt = 1)
if(!silent && body_position != LYING_DOWN)
to_chat(src, span_warning("You are knocked to the floor!"))
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 79c7c7a8032..6e4a0609dd1 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -86,23 +86,21 @@
if(I.force)
var/attack_direction = get_dir(user, src)
apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness(), attack_direction = attack_direction, attacking_item = I)
- if(I.damtype == BRUTE && affecting.can_bleed())
- if(prob(33))
- I.add_mob_blood(src)
- var/turf/location = get_turf(src)
- add_splatter_floor(location)
- if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood
- user.add_mob_blood(src)
- if(affecting.body_zone == BODY_ZONE_HEAD)
- if(wear_mask)
- wear_mask.add_mob_blood(src)
- update_worn_mask()
- if(wear_neck)
- wear_neck.add_mob_blood(src)
- update_worn_neck()
- if(head)
- head.add_mob_blood(src)
- update_worn_head()
+ if(I.damtype == BRUTE && IS_ORGANIC_LIMB(affecting) && prob(I.force * 2))
+ I.add_mob_blood(src)
+ blood_particles(amount = rand(1, 1 + round(I.force/15, 1)), angle = (user == src ? rand(0, 360): get_angle(user, src)))
+ if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood
+ user.add_mob_blood(src)
+ if(affecting.body_zone == BODY_ZONE_HEAD)
+ if(wear_mask)
+ wear_mask.add_mob_blood(src)
+ update_worn_mask()
+ if(wear_neck)
+ wear_neck.add_mob_blood(src)
+ update_worn_neck()
+ if(head)
+ head.add_mob_blood(src)
+ update_worn_head()
return TRUE //successful attack
diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm
index 485082e8862..f7461b93619 100644
--- a/code/modules/mob/living/carbon/death.dm
+++ b/code/modules/mob/living/carbon/death.dm
@@ -3,9 +3,6 @@
return
losebreath = 0
-
- if(!gibbed)
- INVOKE_ASYNC(src, PROC_REF(emote), "deathgasp")
reagents.end_metabolization(src)
add_memory_in_range(src, 7, MEMORY_DEATH, list(DETAIL_PROTAGONIST = src), memory_flags = MEMORY_FLAG_NOMOOD, story_value = STORY_VALUE_OKAY, memory_flags = MEMORY_CHECK_BLIND_AND_DEAF)
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 98ee19b4bc5..0d345483112 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -21,9 +21,9 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift)
if(stat == DEAD)
return
stop_sound_channel(CHANNEL_HEARTBEAT)
- var/obj/item/organ/internal/heart/H = getorganslot(ORGAN_SLOT_HEART)
- if(H)
- H.beat = BEAT_NONE
+ var/obj/item/organ/internal/heart/heart = getorganslot(ORGAN_SLOT_HEART)
+ if(heart)
+ heart.beat = BEAT_NONE
. = ..()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index f06b141b772..dba7f5645d1 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -919,7 +919,7 @@
/mob/living/carbon/human/get_total_bleed_rate()
if(HAS_TRAIT(src, TRAIT_NOBLOOD))
- return FALSE
+ return 0
return ..()
/mob/living/carbon/human/get_exp_list(minutes)
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index d889b71af0f..b11cdbf6af8 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -225,7 +225,7 @@
var/t_his = p_their()
var/t_is = p_are()
//This checks to see if the body is revivable
- if(key || !getorgan(/obj/item/organ/internal/brain) || ghost?.can_reenter_corpse)
+ if(key || !getorgan(ORGAN_SLOT_BRAIN) || ghost?.can_reenter_corpse)
return span_deadsay("[t_He] [t_is] limp and unresponsive; there are no signs of life...")
else
return span_deadsay("[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed...")
diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm
index ba603708583..6e651020769 100644
--- a/code/modules/mob/living/carbon/human/human_update_icons.dm
+++ b/code/modules/mob/living/carbon/human/human_update_icons.dm
@@ -173,17 +173,15 @@ There are several things that need to be remembered:
inv.update_icon()
//Bloody hands begin
- var/mutable_appearance/bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER)
- cut_overlay(bloody_overlay)
if(!gloves && blood_in_hands && (num_hands > 0))
- bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER)
+ var/mutable_appearance/bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER)
if(num_hands < 2)
if(has_left_hand(FALSE))
bloody_overlay.icon_state = "bloodyhands_left"
else if(has_right_hand(FALSE))
bloody_overlay.icon_state = "bloodyhands_right"
- add_overlay(bloody_overlay)
+ overlays_standing[GLOVES_LAYER] = bloody_overlay
//Bloody hands end
var/mutable_appearance/gloves_overlay
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 577b9718830..43793b01778 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1338,15 +1338,13 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(!(prob(25 + (weapon.force * 2))))
return TRUE
- if(affecting.can_bleed())
+ if(IS_ORGANIC_LIMB(affecting) && prob(weapon.force * 2))
+ //blood spatter!
+ bloody = TRUE
weapon.add_mob_blood(human) //Make the weapon bloody, not the person.
- if(prob(weapon.force * 2)) //blood spatter!
- bloody = TRUE
- var/turf/location = human.loc
- if(istype(location))
- human.add_splatter_floor(location)
- if(get_dist(user, human) <= 1) //people with TK won't get smeared with blood
- user.add_mob_blood(human)
+ human.blood_particles(amount = rand(1, 1 + round(weapon.force/15, 1)), angle = (user == human ? rand(0, 360) : get_angle(user, human)))
+ if(get_dist(user, human) <= 1) //people with TK won't get smeared with blood
+ user.add_mob_blood(human)
switch(hit_area)
if(BODY_ZONE_HEAD)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index d4560c151ab..f43043b5424 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -875,8 +875,7 @@
buckled.moving_from_pull = null
return
- var/old_direction = dir
- var/turf/T = loc
+ var/turf/oldloc = loc
if(pulling)
update_pull_movespeed()
@@ -890,8 +889,10 @@
if(active_storage && !((active_storage.parent?.resolve() in important_recursive_contents?[RECURSIVE_CONTENTS_ACTIVE_STORAGE]) || CanReach(active_storage.parent?.resolve(),view_only = TRUE)))
active_storage.hide_contents(src)
- if(body_position == LYING_DOWN && !buckled && prob(getBruteLoss()*200/maxHealth))
- makeTrail(newloc, T, old_direction)
+ if(. && !buckled && (body_position == LYING_DOWN) && prob(getBruteLoss() * MAX_LIVING_HEALTH/maxHealth))
+ var/trail_direction = get_dir(oldloc, newloc)
+ if(trail_direction in GLOB.cardinals)
+ makeTrail(newloc, oldloc, trail_direction)
///Called by mob Move() when the lying_angle is different than zero, to better visually simulate crawling.
@@ -905,43 +906,31 @@
return
/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction)
- if(!has_gravity() || !isturf(start) || !blood_volume)
+ if(!has_gravity() || !isturf(start) || !isturf(target_turf) || !blood_volume || HAS_TRAIT(src, TRAIT_NOBLOOD))
return
- var/blood_exists = locate(/obj/effect/decal/cleanable/trail_holder) in start
-
var/trail_type = getTrail()
if(!trail_type)
return
- var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
- if(blood_volume < max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
- return
-
- var/bleed_amount = bleedDragAmount()
- blood_volume = max(blood_volume - bleed_amount, 0) //that depends on our brute damage.
- var/newdir = get_dir(target_turf, start)
- if(newdir != direction)
- newdir = newdir | direction
- if(newdir == (NORTH|SOUTH))
- newdir = NORTH
- else if(newdir == (EAST|WEST))
- newdir = EAST
- if((newdir in GLOB.cardinals) && (prob(50)))
- newdir = turn(get_dir(target_turf, start), 180)
- if(!blood_exists)
- new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses())
-
- for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
- if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
- TH.existing_dirs += newdir
- TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
- TH.transfer_mob_blood_dna(src)
-
-/mob/living/carbon/human/makeTrail(turf/T)
- if(HAS_TRAIT(src, TRAIT_NOBLOOD) || !is_bleeding() || HAS_TRAIT(src, TRAIT_NOBLEED))
+ var/brute_ratio = clamp(round(getBruteLoss() / maxHealth, 0.1), 0, 1)
+ //don't leave trail if blood volume below a threshold
+ if(blood_volume < (BLOOD_VOLUME_NORMAL * (1 - brute_ratio * 0.25)))
return
- ..()
+ var/bleed_drag_amount = bleedDragAmount()
+ if(!bleed_drag_amount)
+ return
+
+ blood_volume = max(blood_volume - bleed_drag_amount, 0)
+ var/obj/effect/decal/cleanable/blood/trail/start_trail = locate(/obj/effect/decal/cleanable/blood/trail) in start
+ if(start_trail)
+ start_trail.transfer_mob_blood_dna(src)
+ start_trail.add_trail(trail_type, direction)
+ var/obj/effect/decal/cleanable/blood/trail/end_trail = locate(/obj/effect/decal/cleanable/blood/trail) in target_turf
+ if(!end_trail)
+ end_trail = new /obj/effect/decal/cleanable/blood/trail(target_turf, get_static_viruses())
+ end_trail.transfer_mob_blood_dna(src)
+ end_trail.add_trail(trail_type, direction)
///Returns how much blood we're losing from being dragged a tile, from [/mob/living/proc/makeTrail]
/mob/living/proc/bleedDragAmount()
@@ -950,16 +939,15 @@
/mob/living/carbon/bleedDragAmount()
var/bleed_amount = 0
- for(var/i in all_wounds)
- var/datum/wound/iter_wound = i
+ for(var/datum/wound/iter_wound as anything in all_wounds)
bleed_amount += iter_wound.drag_bleed_amount()
return bleed_amount
/mob/living/proc/getTrail()
if(getBruteLoss() < 300)
- return pick("ltrails_1", "ltrails_2")
- else
- return pick("trails_1", "trails_2")
+ return "ltrails"
+ return "trails"
+
/*
/mob/living/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0)
playsound(src, 'sound/effects/space_wind.ogg', 50, TRUE)
@@ -1200,9 +1188,9 @@
/mob/living/carbon/alien/update_stamina()
return
-/mob/living/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
+/mob/living/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
stop_pulling()
- . = ..()
+ return ..()
// Used in polymorph code to shapeshift mobs into other creatures
/**
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 8ff7848d6a5..44dd41351ee 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -464,10 +464,8 @@
return
-/mob/living/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
- if(!used_item)
- used_item = get_active_held_item()
- ..()
+/mob/living/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
+ return ..()
/**
* Does a slap animation on an atom
diff --git a/code/modules/mob/living/silicon/death.dm b/code/modules/mob/living/silicon/death.dm
index 413016519f8..229ad70bb84 100644
--- a/code/modules/mob/living/silicon/death.dm
+++ b/code/modules/mob/living/silicon/death.dm
@@ -5,8 +5,6 @@
new /obj/effect/decal/remains/robot(loc)
/mob/living/silicon/death(gibbed)
- if(!gibbed)
- emote("deathgasp")
diag_hud_set_status()
diag_hud_set_health()
update_health_hud()
diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm
index fc1a8921c06..c0902fb9f41 100644
--- a/code/modules/mob/living/simple_animal/animal_defense.dm
+++ b/code/modules/mob/living/simple_animal/animal_defense.dm
@@ -179,15 +179,15 @@
adjustBruteLoss(20)
return
-/mob/living/simple_animal/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect)
+/mob/living/simple_animal/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = TRUE)
if(!no_effect && !visual_effect_icon && melee_damage_upper)
- if(attack_vis_effect && !iswallturf(A)) // override the standard visual effect.
+ if(attack_vis_effect && !iswallturf(attacked_atom)) // override the standard visual effect.
visual_effect_icon = attack_vis_effect
else if(melee_damage_upper < 10)
visual_effect_icon = ATTACK_EFFECT_PUNCH
else
visual_effect_icon = ATTACK_EFFECT_SMASH
- ..()
+ return ..()
/mob/living/simple_animal/emp_act(severity)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index b86662e97c8..25c8d9619a4 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -322,7 +322,6 @@
target_types += list(
/obj/effect/decal/cleanable/xenoblood,
/obj/effect/decal/cleanable/blood,
- /obj/effect/decal/cleanable/trail_holder,
)
if(pests)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 2feff41c380..d21b0172794 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -150,10 +150,10 @@ Difficulty: Medium
adjustHealth(-2)
return TRUE
-/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
- if(!used_item && !isturf(A))
+/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
+ if(!used_item && !isturf(attacked_atom))
used_item = miner_saw
- ..()
+ return ..()
/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/GiveTarget(new_target)
var/targets_the_same = (new_target == target)
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index 57bb11e23d9..ab1d38f6def 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -44,7 +44,7 @@
/mob/living/simple_animal/shade/death()
if(death_message == initial(death_message))
death_message = "lets out a contented sigh as [p_their()] form unwinds."
- ..()
+ return ..()
/mob/living/simple_animal/shade/canSuicide()
if(istype(loc, /obj/item/soulstone)) //do not suicide inside the soulstone
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index fabe50c20ab..e352001761c 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -392,7 +392,7 @@
///Can the mob hear
/mob/proc/can_hear()
- . = TRUE
+ return TRUE
/**
* Examine text for traits shared by multiple types.
diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm
index 46f116c4254..6db626bb1ee 100644
--- a/code/modules/paperwork/paperplane.dm
+++ b/code/modules/paperwork/paperplane.dm
@@ -84,8 +84,8 @@
return ..()
-/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, quickstart = TRUE)
- . = ..(target, range, speed, thrower, FALSE, diagonals_first, callback, quickstart = quickstart)
+/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin = FALSE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
+ return FALSE
/obj/item/paperplane/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(iscarbon(hit_atom))
diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm
index 4f3eb342759..d40a17de921 100644
--- a/code/modules/projectiles/ammunition/_ammunition.dm
+++ b/code/modules/projectiles/ammunition/_ammunition.dm
@@ -145,8 +145,8 @@
AddComponent(/datum/component/movable_physics, \
physics_flags = MPHYSICS_QDEL_WHEN_NO_MOVEMENT, \
angle = bounce_angle, \
- horizontal_velocity = rand(4.5 * 100, 5.5 * 100) * spread_multiplier * 0.01, \
- vertical_velocity = -rand(4 * 100, 4.5 * 100) * spread_multiplier * 0.01, \
+ horizontal_velocity = rand(4 * 100, 5.5 * 100) * spread_multiplier * 0.01, \
+ vertical_velocity = rand(4 * 100, 4.5 * 100) * spread_multiplier * 0.01, \
horizontal_friction = rand(0.2 * 100, 0.24 * 100) * spread_multiplier * 0.01, \
vertical_friction = 10 * spread_multiplier * 0.05, \
z_floor = 0, \
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 4ef51064f91..6c3b88e8d14 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -229,8 +229,8 @@
/mob/living/carbon/check_limb_hit(hit_zone)
if(get_bodypart(hit_zone))
return hit_zone
- else //when a limb is missing the damage is actually passed to the chest
- return BODY_ZONE_CHEST
+ //when a limb is missing the damage is actually passed to the chest
+ return BODY_ZONE_CHEST
/**
* Called when the projectile hits something
@@ -295,8 +295,8 @@
new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
- if(prob(33))
- L.add_splatter_floor(target_loca)
+ if(prob(damage))
+ L.blood_particles(amount = rand(1, 1 + round(damage/20, 1)), angle = src.Angle)
else if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
diff --git a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm
index 0713446c432..69f00c98a28 100644
--- a/code/modules/spells/spell_types/conjure_item/lighting_packet.dm
+++ b/code/modules/spells/spell_types/conjure_item/lighting_packet.dm
@@ -32,7 +32,7 @@
hit_living.electrocute_act(80, src, flags = SHOCK_ILLUSION)
qdel(src)
-/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE)
+/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = FALSE, diagonals_first = FALSE, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
if(ishuman(thrower))
var/mob/living/carbon/human/human_thrower = thrower
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 9e707e52e5d..c09c9d18804 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -197,6 +197,8 @@
var/any_existing_wound_can_mangle_our_exterior
/// If false, no wound that can be applied to us can mangle our interior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
var/any_existing_wound_can_mangle_our_interior
+ /// A list of all the organs we've got stored inside us
+ var/list/obj/item/organ/organs
/obj/item/bodypart/Initialize(mapload)
. = ..()
@@ -357,7 +359,7 @@
user.visible_message(span_warning("[user] begins to cut open [src]."),\
span_notice("You begin to cut open [src]..."))
if(do_after(user, 54, target = src))
- drop_organs(user, TRUE)
+ drop_organs(user, violent_removal = TRUE)
else
return ..()
@@ -370,19 +372,26 @@
pixel_x = rand(-3, 3)
pixel_y = rand(-3, 3)
-//empties the bodypart from its organs and other things inside it
+/**
+ * Eviscerates the bodypart, dropping all organs and items inside of it
+ * Arguments:
+ * * violent_removal: If TRUE, organs will be thrown out using proc/fly_away() and a splort sound is played
+ */
/obj/item/bodypart/proc/drop_organs(mob/user, violent_removal)
SHOULD_CALL_PARENT(TRUE)
var/atom/drop_loc = drop_location()
- if(IS_ORGANIC_LIMB(src))
+ if(IS_ORGANIC_LIMB(src) && violent_removal)
playsound(drop_loc, 'sound/misc/splort.ogg', 50, TRUE, -1)
seep_gauze(9999) // destroy any existing gauze if any exists
- for(var/obj/item/organ/bodypart_organ in get_organs())
- bodypart_organ.transfer_to_limb(src, owner)
- for(var/obj/item/organ/external/external in external_organs)
- external.remove_from_limb()
- external.forceMove(drop_loc)
+ for(var/obj/item/organ/organ as anything in organs)
+ if(owner)
+ organ.Remove(owner)
+ else
+ organ.remove_from_limb(src)
+ organ.forceMove(drop_loc)
+ if(violent_removal)
+ organ.fly_away(drop_loc)
for(var/obj/item/item_in_bodypart in src)
item_in_bodypart.forceMove(drop_loc)
diff --git a/code/modules/surgery/bodyparts/chest.dm b/code/modules/surgery/bodyparts/chest.dm
new file mode 100644
index 00000000000..f08d9f21425
--- /dev/null
+++ b/code/modules/surgery/bodyparts/chest.dm
@@ -0,0 +1,72 @@
+/obj/item/bodypart/chest
+ name = BODY_ZONE_CHEST
+ desc = "It's impolite to stare at a person's chest."
+ icon_state = "default_human_chest"
+ max_damage = 200
+ body_zone = BODY_ZONE_CHEST
+ body_part = CHEST
+ plaintext_zone = "chest"
+ is_dimorphic = TRUE
+ px_x = 0
+ px_y = 0
+ grind_results = null
+ wound_resistance = 10
+ bodypart_trait_source = CHEST_TRAIT
+ ///The bodytype(s) allowed to attach to this chest.
+ var/acceptable_bodytype = BODYTYPE_HUMANOID
+
+ var/obj/item/cavity_item
+
+/obj/item/bodypart/chest/can_dismember(obj/item/item)
+ if(owner.stat < HARD_CRIT || !get_organs())
+ return FALSE
+ return ..()
+
+/obj/item/bodypart/chest/Destroy()
+ QDEL_NULL(cavity_item)
+ return ..()
+
+/obj/item/bodypart/chest/drop_organs(mob/user, violent_removal)
+ if(cavity_item)
+ cavity_item.forceMove(drop_location())
+ cavity_item = null
+ return ..()
+
+/obj/item/bodypart/chest/monkey
+ icon = 'icons/mob/species/monkey/bodyparts.dmi'
+ icon_static = 'icons/mob/species/monkey/bodyparts.dmi'
+ icon_husk = 'icons/mob/species/monkey/bodyparts.dmi'
+ husk_type = "monkey"
+ top_offset = -5
+ icon_state = "default_monkey_chest"
+ limb_id = SPECIES_MONKEY
+ should_draw_greyscale = FALSE
+ is_dimorphic = FALSE
+ wound_resistance = -10
+ bodytype = BODYTYPE_MONKEY | BODYTYPE_ORGANIC
+ acceptable_bodytype = BODYTYPE_HUMANOID
+ dmg_overlay_type = SPECIES_MONKEY
+
+/obj/item/bodypart/chest/alien
+ icon = 'icons/mob/species/alien/bodyparts.dmi'
+ icon_static = 'icons/mob/species/alien/bodyparts.dmi'
+ icon_state = "alien_chest"
+ limb_id = BODYPART_ID_ALIEN
+ bodytype = BODYTYPE_HUMANOID | BODYTYPE_ALIEN | BODYTYPE_ORGANIC
+ is_dimorphic = FALSE
+ should_draw_greyscale = FALSE
+ bodypart_flags = BODYPART_UNREMOVABLE
+ max_damage = 500
+ acceptable_bodytype = BODYTYPE_HUMANOID
+
+/obj/item/bodypart/chest/larva
+ icon = 'icons/mob/species/alien/bodyparts.dmi'
+ icon_static = 'icons/mob/species/alien/bodyparts.dmi'
+ icon_state = "larva_chest"
+ limb_id = BODYPART_ID_LARVA
+ is_dimorphic = FALSE
+ should_draw_greyscale = FALSE
+ bodypart_flags = BODYPART_UNREMOVABLE
+ max_damage = 50
+ bodytype = BODYTYPE_LARVA_PLACEHOLDER | BODYTYPE_ORGANIC
+ acceptable_bodytype = BODYTYPE_LARVA_PLACEHOLDER
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index aaf70447258..67105ec8c27 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -33,9 +33,7 @@
drop_limb()
limb_owner.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment
- var/turf/owner_location = limb_owner.loc
- if(wounding_type != WOUND_BURN && istype(owner_location) && can_bleed())
- limb_owner.add_splatter_floor(owner_location)
+ limb_owner.bleed(rand(20, 40))
if(QDELETED(src)) //Could have dropped into lava/explosion/chasm/whatever
return TRUE
@@ -43,21 +41,27 @@
return TRUE
if (can_bleed())
add_mob_blood(limb_owner)
- limb_owner.bleed(rand(20, 40))
- var/direction = pick(GLOB.cardinals)
- var/t_range = rand(2,max(throw_range/2, 2))
- var/turf/target_turf = get_turf(src)
- for(var/i in 1 to t_range-1)
- var/turf/new_turf = get_step(target_turf, direction)
- if(!new_turf)
- break
- target_turf = new_turf
- if(new_turf.density)
- break
- throw_at(target_turf, throw_range, throw_speed)
-
+ fly_away(limb_owner.drop_location())
return TRUE
+/// Proc called to initialize movable physics when a bodypart gets dismembered
+/obj/item/bodypart/proc/fly_away(turf/open/owner_location, fly_angle = rand(0, 360))
+ if(!istype(owner_location))
+ return
+ pixel_x = -px_x
+ pixel_y = -px_y
+ return AddComponent(/datum/component/movable_physics, \
+ physics_flags = MPHYSICS_QDEL_WHEN_NO_MOVEMENT, \
+ angle = fly_angle, \
+ horizontal_velocity = rand(2.5 * 100, 6 * 100) * 0.01, \
+ vertical_velocity = rand(4 * 100, 4.5 * 100) * 0.01, \
+ horizontal_friction = rand(0.24 * 100, 0.3 * 100) * 0.01, \
+ vertical_friction = 10 * 0.05, \
+ horizontal_conservation_of_momentum = 0.5, \
+ vertical_conservation_of_momentum = 0.5, \
+ z_floor = 0, \
+ )
+
/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent=TRUE, wounding_type)
if(!owner)
return FALSE
@@ -94,7 +98,6 @@
if(!owner)
return
var/atom/drop_loc = owner.drop_location()
-
SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, dismembered)
SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, dismembered)
update_limb(TRUE)
@@ -165,7 +168,7 @@
return
forceMove(drop_loc)
- SEND_SIGNAL(phantom_owner, COMSIG_CARBON_POST_REMOVE_LIMB, src, dismembered)
+ SEND_SIGNAL(phantom_owner, COMSIG_CARBON_POST_REMOVE_LIMB, src, special, dismembered)
/**
* get_mangled_state() is relevant for flesh and bone bodyparts, and returns whether this bodypart has mangled skin, mangled bone, or both (or neither i guess)
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index 83aa95df61d..18edecbde84 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -138,19 +138,21 @@
return FALSE
return ..()
-/obj/item/bodypart/head/drop_organs(mob/user, violent_removal)
+/obj/item/bodypart/head/drop_organs(mob/user, violent_removal = FALSE)
var/atom/drop_loc = drop_location()
for(var/obj/item/head_item in src)
if(head_item == brain)
if(user)
- user.visible_message(span_warning("[user] saws [src] open and pulls out a brain!"), span_notice("You saw [src] open and pull out a brain."))
+ user.visible_message(span_warning("[user] saws [src] open and pulls out a brain!"), \
+ span_notice("You saw [src] open and pull out a brain."))
if(brainmob)
brainmob.container = null
brainmob.forceMove(brain)
brain.brainmob = brainmob
brainmob = null
- if(violent_removal && prob(rand(80, 100))) //ghetto surgery can damage the brain.
- to_chat(user, span_warning("[brain] was damaged in the process!"))
+ if(violent_removal && prob(80)) //ghetto surgery will likely damage the brain.
+ if(user)
+ to_chat(user, span_warning("\The [brain] was damaged in the process!"))
brain.setOrganDamage(brain.maxHealth)
brain.forceMove(drop_loc)
brain = null
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index f0be7d7b464..f7898af3c2a 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -1,77 +1,3 @@
-
-/obj/item/bodypart/chest
- name = BODY_ZONE_CHEST
- desc = "It's impolite to stare at a person's chest."
- icon_state = "default_human_chest"
- max_damage = 200
- body_zone = BODY_ZONE_CHEST
- body_part = CHEST
- plaintext_zone = "chest"
- is_dimorphic = TRUE
- px_x = 0
- px_y = 0
- grind_results = null
- wound_resistance = 10
- bodypart_trait_source = CHEST_TRAIT
- ///The bodytype(s) allowed to attach to this chest.
- var/acceptable_bodytype = BODYTYPE_HUMANOID
-
- var/obj/item/cavity_item
-
-/obj/item/bodypart/chest/can_dismember(obj/item/item)
- if(owner.stat < HARD_CRIT || !get_organs())
- return FALSE
- return ..()
-
-/obj/item/bodypart/chest/Destroy()
- QDEL_NULL(cavity_item)
- return ..()
-
-/obj/item/bodypart/chest/drop_organs(mob/user, violent_removal)
- if(cavity_item)
- cavity_item.forceMove(drop_location())
- cavity_item = null
- return ..()
-
-/obj/item/bodypart/chest/monkey
- icon = 'icons/mob/species/monkey/bodyparts.dmi'
- icon_static = 'icons/mob/species/monkey/bodyparts.dmi'
- icon_husk = 'icons/mob/species/monkey/bodyparts.dmi'
- husk_type = "monkey"
- top_offset = -5
- icon_state = "default_monkey_chest"
- limb_id = SPECIES_MONKEY
- should_draw_greyscale = FALSE
- is_dimorphic = FALSE
- wound_resistance = -10
- bodytype = BODYTYPE_MONKEY | BODYTYPE_ORGANIC
- acceptable_bodytype = BODYTYPE_HUMANOID
- dmg_overlay_type = SPECIES_MONKEY
-
-/obj/item/bodypart/chest/alien
- icon = 'icons/mob/species/alien/bodyparts.dmi'
- icon_static = 'icons/mob/species/alien/bodyparts.dmi'
- icon_state = "alien_chest"
- limb_id = BODYPART_ID_ALIEN
- bodytype = BODYTYPE_HUMANOID | BODYTYPE_ALIEN | BODYTYPE_ORGANIC
- is_dimorphic = FALSE
- should_draw_greyscale = FALSE
- bodypart_flags = BODYPART_UNREMOVABLE
- max_damage = 500
- acceptable_bodytype = BODYTYPE_HUMANOID
-
-/obj/item/bodypart/chest/larva
- icon = 'icons/mob/species/alien/bodyparts.dmi'
- icon_static = 'icons/mob/species/alien/bodyparts.dmi'
- icon_state = "larva_chest"
- limb_id = BODYPART_ID_LARVA
- is_dimorphic = FALSE
- should_draw_greyscale = FALSE
- bodypart_flags = BODYPART_UNREMOVABLE
- max_damage = 50
- bodytype = BODYTYPE_LARVA_PLACEHOLDER | BODYTYPE_ORGANIC
- acceptable_bodytype = BODYTYPE_LARVA_PLACEHOLDER
-
/// Parent Type for arms, should not appear in game.
/obj/item/bodypart/arm
name = "arm"
diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm
index 23a56449039..f38810b6ca4 100644
--- a/code/modules/surgery/bodyparts/robot_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm
@@ -271,7 +271,7 @@
else
. += span_info("It has a couple spots that still need to be wired.")
-/obj/item/bodypart/chest/robot/drop_organs(mob/user, violent_removal)
+/obj/item/bodypart/chest/robot/drop_organs(mob/user, violent_removal = FALSE)
var/atom/drop_loc = drop_location()
if(wired)
new /obj/item/stack/cable_coil(drop_loc, 1)
@@ -392,7 +392,7 @@
return TRUE
-/obj/item/bodypart/head/robot/drop_organs(mob/user, violent_removal)
+/obj/item/bodypart/head/robot/drop_organs(mob/user, violent_removal = FALSE)
var/atom/drop_loc = drop_location()
if(flash1)
flash1.forceMove(drop_loc)
diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm
index 9331da80a67..41df61c2379 100644
--- a/code/modules/surgery/organs/_organ.dm
+++ b/code/modules/surgery/organs/_organ.dm
@@ -238,17 +238,31 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
//Looking for brains?
//Try code/modules/mob/living/carbon/brain/brain_item.dm
+/// Called on drop_organs for the organ to "fly away" using movable physics
+/obj/item/organ/proc/fly_away(turf/open/owner_location, fly_angle = rand(0, 360))
+ if(!istype(owner_location))
+ return
+ return AddComponent(/datum/component/movable_physics, \
+ physics_flags = MPHYSICS_QDEL_WHEN_NO_MOVEMENT, \
+ angle = fly_angle, \
+ horizontal_velocity = rand(2.5 * 100, 6 * 100) * 0.01, \
+ vertical_velocity = rand(4 * 100, 4.5 * 100) * 0.01, \
+ horizontal_friction = rand(0.24 * 100, 0.3 * 100) * 0.01, \
+ vertical_friction = 10 * 0.05, \
+ horizontal_conservation_of_momentum = 0.5, \
+ vertical_conservation_of_momentum = 0.5, \
+ z_floor = 0, \
+ )
+
/**
* Heals all of the mob's organs, and re-adds any missing ones.
*
* * regenerate_existing - if TRUE, existing organs will be deleted and replaced with new ones
*/
/mob/living/carbon/proc/regenerate_organs(regenerate_existing = FALSE)
-
// Delegate to species if possible.
if(dna?.species)
dna.species.regenerate_organs(src, replace_current = regenerate_existing)
-
// Species regenerate organs doesn't ALWAYS handle healing the organs because it's dumb
for(var/obj/item/organ/organ as anything in internal_organs)
organ.setOrganDamage(0)
diff --git a/code/modules/vehicles/mecha/mecha_defense.dm b/code/modules/vehicles/mecha/mecha_defense.dm
index 2f1b6daf5d1..aa7d0817ff4 100644
--- a/code/modules/vehicles/mecha/mecha_defense.dm
+++ b/code/modules/vehicles/mecha/mecha_defense.dm
@@ -360,14 +360,14 @@
/obj/vehicle/sealed/mecha/narsie_act()
emp_act(EMP_HEAVY)
-/obj/vehicle/sealed/mecha/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
+/obj/vehicle/sealed/mecha/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect = FALSE, angled = FALSE)
if(!no_effect && !visual_effect_icon)
visual_effect_icon = ATTACK_EFFECT_SMASH
if(damtype == BURN)
visual_effect_icon = ATTACK_EFFECT_MECHFIRE
else if(damtype == TOX)
visual_effect_icon = ATTACK_EFFECT_MECHTOXIN
- ..()
+ return ..()
/obj/vehicle/sealed/mecha/proc/ammo_resupply(obj/item/mecha_ammo/A, mob/user,fail_chat_override = FALSE)
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 663751285f1..f98148083a7 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -745,7 +745,7 @@ GLOBAL_LIST_EMPTY(vending_products)
if(O.dismember())
C.visible_message(span_danger("[O] explodes in a shower of gore beneath [src]!"), \
span_userdanger("Oh f-"))
- O.drop_organs()
+ O.drop_organs(violent_removal = TRUE)
qdel(O)
new /obj/effect/gibspawner/human/bodypartless(get_turf(C))
diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi
index c2bfc76119d..8ba95269321 100644
Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 9d9f4c852a1..011241c084d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1580,6 +1580,10 @@
#include "code\game\objects\effects\decals\cleanable\humans.dm"
#include "code\game\objects\effects\decals\cleanable\misc.dm"
#include "code\game\objects\effects\decals\cleanable\robots.dm"
+#include "code\game\objects\effects\decals\cleanable\blood\hitsplatter.dm"
+#include "code\game\objects\effects\decals\cleanable\blood\particle.dm"
+#include "code\game\objects\effects\decals\cleanable\blood\squirt.dm"
+#include "code\game\objects\effects\decals\cleanable\blood\trail.dm"
#include "code\game\objects\effects\decals\turfdecal\dirt.dm"
#include "code\game\objects\effects\decals\turfdecal\markings.dm"
#include "code\game\objects\effects\decals\turfdecal\tilecoloring.dm"
@@ -4651,6 +4655,7 @@
#include "code\modules\surgery\advanced\bioware\nerve_splicing.dm"
#include "code\modules\surgery\advanced\bioware\vein_threading.dm"
#include "code\modules\surgery\bodyparts\_bodyparts.dm"
+#include "code\modules\surgery\bodyparts\chest.dm"
#include "code\modules\surgery\bodyparts\dismemberment.dm"
#include "code\modules\surgery\bodyparts\hair.dm"
#include "code\modules\surgery\bodyparts\head.dm"