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"