diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index b15f8c785d13..a8ff9535fb12 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -108,8 +108,9 @@ * * silent: Not actually necessary I don't think, was originally used for demoting wounds so they wouldn't make new messages, but I believe old_wound took over that, I may remove this shortly * * old_wound: If our new wound is a replacement for one of the same time (promotion or demotion), we can reference the old one just before it's removed to copy over necessary vars * * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented) + * * attack_direction: For bloodsplatters, if relevant */ -/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE) +/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null) if(!istype(L) || !L.owner || !(L.body_zone in viable_zones) || isalien(L.owner) || !L.is_organic_limb()) qdel(src) return @@ -159,7 +160,7 @@ playsound(L.owner, sound_effect, 70 + 20 * severity, TRUE) if(!demoted) - wound_injury(old_wound) + wound_injury(old_wound, attack_direction = attack_direction) second_wind() /// Remove the wound from whatever it's afflicting, and cleans up whateverstatus effects it had or modifiers it had on interaction times. ignore_limb is used for detachments where we only want to forget the victim @@ -190,16 +191,16 @@ * * new_type- The TYPE PATH of the wound you want to replace this, like /datum/wound/slash/severe * * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented) */ -/datum/wound/proc/replace_wound(new_type, smited = FALSE) +/datum/wound/proc/replace_wound(new_type, smited = FALSE, attack_direction = attack_direction) var/datum/wound/new_wound = new new_type already_scarred = TRUE remove_wound(replaced=TRUE) - new_wound.apply_wound(limb, old_wound = src, smited = smited) + new_wound.apply_wound(limb, old_wound = src, smited = smited, attack_direction = attack_direction) . = new_wound qdel(src) /// The immediate negative effects faced as a result of the wound -/datum/wound/proc/wound_injury(datum/wound/old_wound = null) +/datum/wound/proc/wound_injury(datum/wound/old_wound = null, attack_direction = null) return /// Proc called to change the variable `limb` and react to the event. @@ -313,7 +314,7 @@ return (!QDELETED(src) && limb) /// When our parent bodypart is hurt -/datum/wound/proc/receive_damage(wounding_type, wounding_dmg, wound_bonus) +/datum/wound/proc/receive_damage(wounding_type, wounding_dmg, wound_bonus, attack_direction) return /// Called from cryoxadone and pyroxadone when they're proc'ing. Wounds will slowly be fixed separately from other methods when these are in effect. crappy name but eh diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 61314df7bd69..991334b2447f 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -35,7 +35,7 @@ /* Overwriting of base procs */ -/datum/wound/blunt/wound_injury(datum/wound/old_wound = null) +/datum/wound/blunt/wound_injury(datum/wound/old_wound = null, attack_direction = null) // hook into gaining/losing gauze so crit bone wounds can re-enable/disable depending if they're slung or not RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), .proc/update_inefficiencies) @@ -348,7 +348,7 @@ regen_ticks_needed = 240 // ticks every 2 seconds, 480 seconds, so roughly 8 minutes default // doesn't make much sense for "a" bone to stick out of your head -/datum/wound/blunt/critical/apply_wound(obj/item/bodypart/L, silent, datum/wound/old_wound, smited) +/datum/wound/blunt/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited, attack_direction = null) if(L.body_zone == BODY_ZONE_HEAD) occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood" examine_desc = "has an unsettling indent, with bits of skull poking out" diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm index 22dd877e4747..bfafef60abbb 100644 --- a/code/datums/wounds/loss.dm +++ b/code/datums/wounds/loss.dm @@ -12,7 +12,7 @@ already_scarred = TRUE // We manually assign scars for dismembers through endround missing limbs and aheals /// Our special proc for our special dismembering, the wounding type only matters for what text we have -/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH, outright = FALSE) +/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH, outright = FALSE, attack_direction) if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in viable_zones) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember()) qdel(src) return @@ -49,6 +49,8 @@ set_limb(dismembered_part) second_wind() log_wound(victim, src) + if(wounding_type != WOUND_BURN && victim.blood_volume) + victim.spray_blood(attack_direction, severity) dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE) qdel(src) return TRUE diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 47e7f4dd9bb4..eccf9fd1fc75 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -23,8 +23,10 @@ /// If we let off blood when hit, the max blood lost is this * the incoming damage var/internal_bleeding_coefficient -/datum/wound/pierce/wound_injury(datum/wound/old_wound) +/datum/wound/pierce/wound_injury(datum/wound/old_wound = null, attack_direction = null) blood_flow = initial_flow + if(attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY(victim)) + victim.spray_blood(attack_direction, severity) /datum/wound/pierce/receive_damage(wounding_type, wounding_dmg, wound_bonus) if(victim.stat == DEAD || wounding_dmg < 5) diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index cf480b627dea..c1c2a070f450 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -30,13 +30,15 @@ /// A bad system I'm using to track the worst scar we earned (since we can demote, we want the biggest our wound has been, not what it was when it was cured (probably moderate)) var/datum/scar/highest_scar -/datum/wound/slash/wound_injury(datum/wound/slash/old_wound = null) +/datum/wound/slash/wound_injury(datum/wound/slash/old_wound = null, attack_direction = null) blood_flow = initial_flow if(old_wound) blood_flow = max(old_wound.blood_flow, initial_flow) if(old_wound.severity > severity && old_wound.highest_scar) highest_scar = old_wound.highest_scar old_wound.highest_scar = null + else if(attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY(victim)) + victim.spray_blood(attack_direction, severity) if(!highest_scar) highest_scar = new diff --git a/code/game/atoms.dm b/code/game/atoms.dm index ff5b6d14fa4d..5e40cc94ea62 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1270,7 +1270,7 @@ * * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present * * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods */ -/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll) +/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll, attack_direction = null) if(QDELETED(victim) || !suffered_wound) return var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote? @@ -1287,6 +1287,9 @@ if(dealt_bare_wound_bonus) message += " | BWB: [dealt_bare_wound_bonus]" + if(attack_direction) + message += " | AtkDir: [attack_direction]" + victim.log_message(message, LOG_ATTACK, color="blue") diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 74e0f3b3f888..1eccde26ad05 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -296,6 +296,17 @@ for(var/atom/movable/AM in buckled_mobs) AM.set_glide_size(target) +/** + * meant for movement with zero side effects. only use for objects that are supposed to move "invisibly" (like camera mobs or ghosts) + * if you want something to move onto a tile with a beartrap or recycler or tripmine or mouse without that object knowing about it at all, use this + * most of the time you want forceMove() + */ +/atom/movable/proc/abstract_move(atom/new_loc) + var/atom/old_loc = loc + var/direction = get_dir(old_loc, new_loc) + loc = new_loc + Moved(old_loc, direction, TRUE) + //////////////////////////////////////// // Here's where we rewrite how byond handles movement except slightly different // To be removed on step_ conversion diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index b22c2dd9150e..6a355280c520 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -29,6 +29,12 @@ icon_state = "gibbl1" random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") +/obj/effect/decal/cleanable/blood/splatter/over_window // special layer/plane set to appear on windows + layer = ABOVE_WINDOW_LAYER + plane = GAME_PLANE + turf_loc_check = FALSE + alpha = 180 + /obj/effect/decal/cleanable/blood/tracks icon_state = "tracks" desc = "They look like tracks left by wheels." @@ -209,8 +215,102 @@ /obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return 1 - return 0 + 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 + +/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. Contains sleep() pending imminent moveloop rework, don't call without async'ing it +/obj/effect/decal/cleanable/blood/hitsplatter/proc/fly_towards(turf/target_turf, range) + for(var/i in 1 to range) + step_towards(src,target_turf) + sleep(2) // Will be resolved pending Potato's moveloop rework + prev_loc = loc + for(var/atom/iter_atom in get_turf(src)) + if(hit_endpoint) + return + if(splatter_strength <= 0) + break + + if(isitem(iter_atom)) + iter_atom.add_blood_DNA(blood_dna_info) + splatter_strength-- + else if(ishuman(iter_atom)) + var/mob/living/carbon/human/splashed_human = iter_atom + if(splashed_human.wear_suit) + splashed_human.wear_suit.add_blood_DNA(blood_dna_info) + splashed_human.update_inv_wear_suit() //updates mob overlays to show the new blood (no refresh) + if(splashed_human.w_uniform) + splashed_human.w_uniform.add_blood_DNA(blood_dna_info) + splashed_human.update_inv_w_uniform() //updates mob overlays to show the new blood (no refresh) + splatter_strength-- + if(splatter_strength <= 0) // we used all the puff so we delete it. + qdel(src) + return + 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) /obj/effect/decal/cleanable/blood/kilo name = "remember kilo" diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm index cc7acc7fd760..91afba114166 100644 --- a/code/game/objects/items/clown_items.dm +++ b/code/game/objects/items/clown_items.dm @@ -121,7 +121,12 @@ if(do_after(user, src.cleanspeed, target)) to_chat(user, span_notice("You clean \the [target.name].")) target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) + var/obj/structure/window/our_window = target + if(our_window.bloodied) + for(var/obj/effect/decal/cleanable/blood/iter_blood in our_window) + our_window.vis_contents -= iter_blood + qdel(iter_blood) + our_window.bloodied = FALSE decreaseUses(user) else user.visible_message("[user] begins to clean \the [target.name] with [src]...", span_notice("You begin to clean \the [target.name] with [src]...")) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 4d8026962a2d..58335e70f91f 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -25,8 +25,9 @@ var/mutable_appearance/crack_overlay var/real_explosion_block //ignore this, just use explosion_block var/breaksound = "shatter" - var/hitsound = 'sound/effects/Glasshit.ogg' - + var/hitsound = 'sound/effects/Glasshit.ogg' + /// If some inconsiderate jerk has had their blood spilled on this window, thus making it cleanable + var/bloodied = FALSE /obj/structure/window/examine(mob/user) . = ..() diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm index e2021e93301f..c51d79d7f263 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -56,3 +56,10 @@ for(var/mob/living/enterer as anything in arrived.get_all_contents_type(/mob/living)) to_chat(enterer, span_userdanger("This was a bad idea...")) enterer.dust(TRUE, FALSE, TRUE) + +/// This type of cordon will block ghosts from passing through it. Useful for stuff like Away Missions, where you feasibly want to block ghosts from entering to keep a certain map section a secret. +/turf/cordon/secret + name = "secret cordon (ghost blocking)" + +/turf/cordon/secret/attack_ghost(mob/dead/observer/user) + return FALSE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index c1cae5ad0309..1e7164b6b071 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1305,3 +1305,21 @@ /mob/living/carbon/proc/force_drink_text(obj/O, mob/living/carbon/C, mob/user) return dna?.species.force_drink_text(O, C, user) + +/** + * 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(return_blood_DNA()) + our_splatter.blood_dna_info = get_blood_dna_list() + var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength) + INVOKE_ASYNC(our_splatter, /obj/effect/decal/cleanable/blood/hitsplatter/.proc/fly_towards, targ, splatter_strength) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 2ce3853bdfc9..8148b0ac8072 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -179,8 +179,9 @@ affecting = bodyparts[1] SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) send_item_attack_message(I, user, affecting.name, affecting) - if(I.force) - apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.sharpness) + 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.sharpness, attack_direction = attack_direction) if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC) if(prob(33)) I.add_mob_blood(src) diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index d8792357e901..0154cf6e0784 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -1,6 +1,6 @@ -/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) +/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone) var/hit_percent = (100-blocked)/100 if(!damage || hit_percent <= 0) @@ -19,13 +19,13 @@ switch(damagetype) if(BRUTE) if(BP) - if(BP.receive_damage(damage * hit_percent, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness)) + if(BP.receive_damage(damage * hit_percent, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) update_damage_overlays() else //no bodypart, we deal damage with a more general method. adjustBruteLoss(damage * hit_percent) if(BURN) if(BP) - if(BP.receive_damage(0, damage * hit_percent, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness)) + if(BP.receive_damage(0, damage * hit_percent, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) update_damage_overlays() else adjustFireLoss(damage * hit_percent) diff --git a/code/modules/mob/living/carbon/human/damage_procs.dm b/code/modules/mob/living/carbon/human/damage_procs.dm index 560e56e273b0..975177b4742b 100644 --- a/code/modules/mob/living/carbon/human/damage_procs.dm +++ b/code/modules/mob/living/carbon/human/damage_procs.dm @@ -1,7 +1,7 @@ -/mob/living/carbon/human/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) - return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, wound_bonus, bare_wound_bonus, sharpness) +/mob/living/carbon/human/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) + return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, wound_bonus, bare_wound_bonus, sharpness, attack_direction) /mob/living/carbon/human/revive(full_heal = 0, admin_revive = 0) if(..()) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 410ba492eea6..4fa8aae26d79 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -354,7 +354,8 @@ if(!affecting) affecting = get_bodypart(BODY_ZONE_CHEST) var/armor = run_armor_check(affecting, MELEE, armour_penetration = M.armour_penetration) - apply_damage(damage, M.melee_damage_type, affecting, armor, wound_bonus = M.wound_bonus, bare_wound_bonus = M.bare_wound_bonus, sharpness = M.sharpness) + var/attack_direction = get_dir(M, src) + apply_damage(damage, M.melee_damage_type, affecting, armor, wound_bonus = M.wound_bonus, bare_wound_bonus = M.bare_wound_bonus, sharpness = M.sharpness, attack_direction = attack_direction) /mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 6d8358118bb2..923765d77ee8 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1584,11 +1584,12 @@ GLOBAL_LIST_EMPTY(features_by_species) if(user.limb_destroyer) target.dismembering_strike(user, affecting.body_zone) + var/attack_direction = get_dir(user, target) if(atk_verb == ATTACK_EFFECT_KICK)//kicks deal 1.5x raw damage - target.apply_damage(damage*1.5, user.dna.species.attack_type, affecting, armor_block) + target.apply_damage(damage*1.5, user.dna.species.attack_type, affecting, armor_block, attack_direction = attack_direction) log_combat(user, target, "kicked") else//other attacks deal full raw damage + 1.5x in stamina damage - target.apply_damage(damage, user.dna.species.attack_type, affecting, armor_block) + target.apply_damage(damage, user.dna.species.attack_type, affecting, armor_block, attack_direction = attack_direction) target.apply_damage(damage*1.5, STAMINA, affecting, armor_block) log_combat(user, target, "punched") @@ -1778,7 +1779,8 @@ GLOBAL_LIST_EMPTY(features_by_species) H.send_item_attack_message(I, user, hit_area, affecting) - apply_damage(I.force * weakness, I.damtype, def_zone, armor_block, H, wound_bonus = Iwound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.sharpness) + var/attack_direction = get_dir(user, H) + apply_damage(I.force * weakness, I.damtype, def_zone, armor_block, H, wound_bonus = Iwound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.sharpness, attack_direction = attack_direction) if(!I.force) return FALSE //item force is zero @@ -1845,8 +1847,8 @@ GLOBAL_LIST_EMPTY(features_by_species) H.forcesay(GLOB.hit_appends) //forcesay checks stat already. return TRUE -/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) - SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness) // make sure putting wound_bonus here doesn't screw up other signals or uses for this signal) +/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) + SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness, attack_direction) // make sure putting wound_bonus here doesn't screw up other signals or uses for this signal) var/hit_percent = (100-(blocked+armor))/100 hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100 if(!damage || hit_percent <= 0) @@ -1866,14 +1868,14 @@ GLOBAL_LIST_EMPTY(features_by_species) if(BRUTE) H.damageoverlaytemp = 20 if(BP) - if(BP.receive_damage(damage * hit_percent * brutemod * H.physiology.brute_mod, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness)) + if(BP.receive_damage(damage * hit_percent * brutemod * H.physiology.brute_mod, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) H.update_damage_overlays() else//no bodypart, we deal damage with a more general method. H.adjustBruteLoss(damage * hit_percent * brutemod * H.physiology.brute_mod) if(BURN) H.damageoverlaytemp = 20 if(BP) - if(BP.receive_damage(0, damage * hit_percent * burnmod * H.physiology.burn_mod, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness)) + if(BP.receive_damage(0, damage * hit_percent * burnmod * H.physiology.burn_mod, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness, attack_direction = attack_direction)) H.update_damage_overlays() else H.adjustFireLoss(damage * hit_percent * burnmod * H.physiology.burn_mod) diff --git a/code/modules/mob/living/carbon/human/species_types/eggpeople.dm b/code/modules/mob/living/carbon/human/species_types/eggpeople.dm index 9fbee5cc5017..d23840917067 100644 --- a/code/modules/mob/living/carbon/human/species_types/eggpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/eggpeople.dm @@ -26,7 +26,7 @@ screamsound = 'yogstation/sound/voice/eggperson/egg_scream.ogg' // (Hopefully) the sound of an egg cracking species_language_holder = /datum/language_holder/egg -/datum/species/egg/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE) +/datum/species/egg/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE, attack_direction = null) if(damagetype == BRUTE) // Dynamic brute resist based on burn damage. The more fried the egg, the harder the shell!! var/x = H.getFireLoss() brutemod = EGG_ALPHA * x*x + EGG_BETA * x + EGG_MAXBRUTEMOD //A polynomial, to determine how much brute we take. https://www.desmos.com/calculator/dwxdxwt0rl diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index bc7ac579f7b3..92841032dd5e 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -63,7 +63,7 @@ /datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount) . = min(20, amount) -/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) +/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) . = ..() if(.) regen_cooldown = world.time + REGENERATION_DELAY @@ -153,7 +153,7 @@ /datum/species/zombie/infectious/gamemode/spec_stun(mob/living/carbon/human/H,amount) . = 0 -/datum/species/zombie/infectious/gamemode/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE) +/datum/species/zombie/infectious/gamemode/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE, attack_direction = null) if(damagetype == STAMINA) return . = ..() diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index d5a2b0de3012..b27c8d5512a9 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -8,7 +8,7 @@ Returns standard 0 if fail */ -/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) +/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone) var/hit_percent = (100-blocked)/100 if(!damage || (hit_percent <= 0)) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 07e0ed8d7bcd..d55e42974dc8 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -71,7 +71,8 @@ if((istype(P, /obj/item/projectile/energy/nuclear_particle)) && (getarmor(null, RAD) >= 100)) P.damage = 0 else - apply_damage(P.damage, P.damage_type, def_zone, armor, wound_bonus = P.wound_bonus, bare_wound_bonus = P.bare_wound_bonus, sharpness = P.get_sharpness()) + var/attack_direction = get_dir(P.starting, src) + apply_damage(P.damage, P.damage_type, def_zone, armor, wound_bonus = P.wound_bonus, bare_wound_bonus = P.bare_wound_bonus, sharpness = P.get_sharpness(), attack_direction = attack_direction) if(P.dismemberment) check_projectile_dismemberment(P, def_zone) if(P.penetrating && (P.penetration_type == 0 || P.penetration_type == 2) && P.penetrations > 0) diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm index 7926d74d128d..a4a417c2857f 100644 --- a/code/modules/mob/living/silicon/damage_procs.dm +++ b/code/modules/mob/living/silicon/damage_procs.dm @@ -1,5 +1,5 @@ -/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) +/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) var/hit_percent = (100-blocked)/100 if(!damage || (hit_percent <= 0)) return 0 diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 7bbfac6b9118..160b7c86ecc1 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -513,6 +513,12 @@ if(zMove(DOWN, TRUE)) to_chat(src, span_notice("You move down.")) +/mob/abstract_move(atom/destination) + var/turf/new_turf = get_turf(destination) + if(new_turf && (istype(new_turf, /turf/cordon/secret) || is_secret_level(new_turf.z)) && !client?.holder) + return + return ..() + ///Move a mob between z levels, if it's valid to move z's on this turf /mob/proc/zMove(dir, feedback = FALSE) if(dir != UP && dir != DOWN) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index c0ec443dbe70..fcbfaa5b64ec 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -208,7 +208,7 @@ //Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all. //Damage will not exceed max_damage using this proc //Cannot apply negative damage -/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm +/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm var/hit_percent = (100-blocked)/100 if((!brute && !burn && !stamina) || hit_percent <= 0) return FALSE @@ -280,7 +280,7 @@ // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage if(owner && wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND) - check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus) + check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, attack_direction) for(var/i in wounds) var/datum/wound/iter_wound = i @@ -373,7 +373,7 @@ * * wound_bonus- The wound_bonus of an attack * * bare_wound_bonus- The bare_wound_bonus of an attack */ -/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus) +/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus, attack_direction) // note that these are fed into an exponent, so these are magnified if(HAS_TRAIT(owner, TRAIT_EASILY_WOUNDED)) damage *= 1.5 @@ -396,7 +396,7 @@ if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && (get_damage() / max_damage * 50)) var/datum/wound/loss/dismembering = new - dismembering.apply_dismember(src, woundtype, outright=TRUE) + dismembering.apply_dismember(src, woundtype, outright = TRUE, attack_direction = attack_direction) return // quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_wounding_mods() somehow @@ -426,11 +426,11 @@ var/datum/wound/new_wound if(replaced_wound) new_wound = replaced_wound.replace_wound(possible_wound) - log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned + log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll, attack_direction = attack_direction) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned else new_wound = new possible_wound new_wound.apply_wound(src) - log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) + log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll, attack_direction = attack_direction) return new_wound // try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi index c78f2a1311cf..d2d0290dc9de 100644 Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ diff --git a/sound/effects/wounds/splatter.ogg b/sound/effects/wounds/splatter.ogg new file mode 100644 index 000000000000..1c678cfe1268 Binary files /dev/null and b/sound/effects/wounds/splatter.ogg differ diff --git a/sound/weapons/attributions.txt b/sound/weapons/attributions.txt index d42313fe0619..16a464025ce0 100644 --- a/sound/weapons/attributions.txt +++ b/sound/weapons/attributions.txt @@ -16,3 +16,5 @@ revolverspin1, revolverspin2, and revolverspin3 are cut from a recording captured by the BBC "Gunfire & Guns: Revolver, Chambers spinning.": http://bbcsfx.acropolis.org.uk/assets/07027165.wav bbc.co.uk - (c) copyright 2018 BBC + +splatter.ogg adapted from https://freesound.org/people/Rocktopus/sounds/233418/