diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index dd5b38113d3..677a65a7be1 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -25,6 +25,8 @@ // /mob/living/carbon physiology signals #define COMSIG_CARBON_GAIN_WOUND "carbon_gain_wound" //from /datum/wound/proc/apply_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L) #define COMSIG_CARBON_LOSE_WOUND "carbon_lose_wound" //from /datum/wound/proc/remove_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L) +/// Called after limb AND victim has been unset +#define COMSIG_CARBON_POST_LOSE_WOUND "carbon_post_lose_wound" //from /datum/wound/proc/remove_wound() (/datum/wound/lost_wound, /obj/item/bodypart/part, ignore_limb, replaced) ///from base of /obj/item/bodypart/proc/can_attach_limb(): (new_limb, special) allows you to fail limb attachment #define COMSIG_ATTEMPT_CARBON_ATTACH_LIMB "attempt_carbon_attach_limb" #define COMPONENT_NO_ATTACH (1<<0) diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index 1d377ac4121..2d0cc5e079e 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -1,4 +1,3 @@ - // ~wound damage/rolling defines /// the cornerstone of the wound threshold system, your base wound roll for any attack is rand(1, damage^this), after armor reduces said damage. See [/obj/item/bodypart/proc/check_wounding] #define WOUND_DAMAGE_EXPONENT 1.4 @@ -13,6 +12,10 @@ /// set wound_bonus on an item or attack to this to disable checking wounding for the attack #define CANT_WOUND -100 +/// If there are multiple possible and valid wounds for the same type and severity, weight will be used to pick among them. See _wound_pregen_data.dm for more details +/// This is used in pick_weight, so use integers +#define WOUND_DEFAULT_WEIGHT 50 + // ~wound severities /// for jokey/meme wounds like stubbed toe, no standard messages/sounds or second winds #define WOUND_SEVERITY_TRIVIAL 0 @@ -22,16 +25,26 @@ /// outright dismemberment of limb #define WOUND_SEVERITY_LOSS 4 +/// A "chronological" list of wound severities, starting at the least severe. +GLOBAL_LIST_INIT(wound_severities_chronological, list( + "[WOUND_SEVERITY_TRIVIAL]", + "[WOUND_SEVERITY_MODERATE]", + "[WOUND_SEVERITY_SEVERE]", + "[WOUND_SEVERITY_CRITICAL]" +)) -// ~wound categories +// ~wound categories: wounding_types /// any brute weapon/attack that doesn't have sharpness. rolls for blunt bone wounds -#define WOUND_BLUNT 1 +#define WOUND_BLUNT "wound_blunt" /// any brute weapon/attack with sharpness = SHARP_EDGED. rolls for slash wounds -#define WOUND_SLASH 2 +#define WOUND_SLASH "wound_slash" /// any brute weapon/attack with sharpness = SHARP_POINTY. rolls for piercing wounds -#define WOUND_PIERCE 3 +#define WOUND_PIERCE "wound_pierce" /// any concentrated burn attack (lasers really). rolls for burning wounds -#define WOUND_BURN 4 +#define WOUND_BURN "wound_burn" + +/// Mainly a define used for wound_pregen_data, if a pregen data instance expects this, it will accept any and all wound types, even none at all +#define WOUND_ALL "wound_all" // ~determination second wind defines @@ -46,6 +59,11 @@ /// While someone has determination in their system, their bleed rate is slightly reduced #define WOUND_DETERMINATION_BLEED_MOD 0.85 +/// Wounds using this competition mode will remove any wounds of a greater severity than itself in a random wound roll. In most cases, you dont want to use this. +#define WOUND_COMPETITION_OVERPOWER_GREATERS "wound_submit" +/// Wounds using this competition mode will remove any wounds of a lower severity than itself in a random wound roll. Used for ensuring the worse case scenario of a given injury_roll. +#define WOUND_COMPETITION_OVERPOWER_LESSERS "wound_dominate" + // ~biology defines // What kind of biology a limb has, and what wounds it can suffer /// Has absolutely fucking nothing, no wounds @@ -54,36 +72,43 @@ #define BIO_BONE (1<<0) /// Has flesh - allows the victim to suffer fleshy slash pierce and burn wounds #define BIO_FLESH (1<<1) -/// Self explanatory -#define BIO_FLESH_BONE (BIO_BONE | BIO_FLESH) /// Has metal - allows the victim to suffer robotic blunt and burn wounds #define BIO_METAL (1<<2) /// Is wired internally - allows the victim to suffer electrical wounds (robotic T1-T3 slash/pierce) #define BIO_WIRED (1<<3) -/// Robotic: shit like cyborg limbs, mostly -#define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) /// Has bloodflow - can suffer bleeding wounds and can bleed #define BIO_BLOODED (1<<4) /// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) #define BIO_JOINTED (1<<5) -/// Standard humanoid - can suffer all flesh wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed -#define BIO_STANDARD (BIO_FLESH_BONE|BIO_BLOODED) +/// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko +#define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) +/// Has flesh and bone - See BIO_BONE and BIO_FLESH +#define BIO_FLESH_BONE (BIO_BONE|BIO_FLESH) +/// Standard humanoid - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt, except dislocations. Think human heads/chests +#define BIO_STANDARD_UNJOINTED (BIO_FLESH_BONE|BIO_BLOODED) +/// Standard humanoid limbs - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed, and be dislocated. Think human arms and legs +#define BIO_STANDARD_JOINTED (BIO_STANDARD_UNJOINTED|BIO_JOINTED) -// "Where" a specific "bio" feature is within a given limb -// Exterior is hard shit, the last line, shit lines bones -// Interior is soft shit, targetted by slashes and pierces (usually), protects exterior -// Yes, it makes no sense -/// The given biostate is on the "exterior" of the limb - hard shit, protected by interior -#define BIO_EXTERIOR (1<<0) -/// The given biostate is on the "exterior" of the limb - soft shit, protects exterior -#define BIO_INTERIOR (1<<1) -#define BIO_EXTERIOR_AND_INTERIOR (BIO_EXTERIOR|BIO_INTERIOR) +// "Where" a specific biostate is within a given limb +// Interior is hard shit, the last line, shit like bones +// Exterior is soft shit, targetted by slashes and pierces (usually), protects exterior +// A limb needs both mangled interior and exterior to be dismembered, but slash/pierce must mangle exterior to attack the interior +// Not having exterior/interior counts as mangled exterior/interior for the purposes of dismemberment +/// The given biostate is on the "interior" of the limb - hard shit, protected by exterior +#define ANATOMY_INTERIOR (1<<0) +/// The given biostate is on the "exterior" of the limb - soft shit, protects interior +#define ANATOMY_EXTERIOR (1<<1) +#define ANATOMY_EXTERIOR_AND_INTERIOR (ANATOMY_EXTERIOR|ANATOMY_INTERIOR) -GLOBAL_LIST_INIT(bio_state_states, list( - "[BIO_WIRED]" = BIO_INTERIOR, - "[BIO_METAL]" = BIO_EXTERIOR, - "[BIO_FLESH]" = BIO_INTERIOR, - "[BIO_BONE]" = BIO_EXTERIOR, +/// A assoc list of BIO_ define to EXTERIOR/INTERIOR defines. +/// This is where the interior/exterior state of a given biostate is set. +/// Note that not all biostates are guaranteed to be one of these - and in fact, many are not +/// IMPORTANT NOTE: All keys are stored as text and must be converted via text2num +GLOBAL_LIST_INIT(bio_state_anatomy, list( + "[BIO_WIRED]" = ANATOMY_EXTERIOR, + "[BIO_METAL]" = ANATOMY_INTERIOR, + "[BIO_FLESH]" = ANATOMY_EXTERIOR, + "[BIO_BONE]" = ANATOMY_INTERIOR, )) // Wound series @@ -91,13 +116,169 @@ GLOBAL_LIST_INIT(bio_state_states, list( // Multiple wounds in a single series cannot be on a limb - the highest severity will always be prioritized, and lower ones will be skipped /// T1-T3 Bleeding slash wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: slash.dm -#define WOUND_SERIES_FLESH_SLASH_BLEED 1 +#define WOUND_SERIES_FLESH_SLASH_BLEED "wound_series_flesh_slash_bled" /// T1-T3 Basic blunt wounds. T1 requires jointed, but 2-3 require bone. From: bone.dm -#define WOUND_SERIES_BONE_BLUNT_BASIC 2 +#define WOUND_SERIES_BONE_BLUNT_BASIC "wound_series_bone_blunt_basic" /// T1-T3 Basic burn wounds. Requires flesh. From: burns.dm -#define WOUND_SERIES_FLESH_BURN_BASIC 3 -/// T1-3 Bleeding puncture wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: pierce.dm -#define WOUND_SERIES_FLESH_PUNCTURE_BLEED 4 +#define WOUND_SERIES_FLESH_BURN_BASIC "wound_series_flesh_burn_basic" +/// T1-T3 Bleeding puncture wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: pierce.dm +#define WOUND_SERIES_FLESH_PUNCTURE_BLEED "wound_series_flesh_puncture_bleed" +/// Generic loss wounds. See loss.dm +#define WOUND_SERIES_LOSS_BASIC "wound_series_loss_basic" + +/// A assoc list of (wound typepath -> wound_pregen_data instance). Every wound should have a pregen data. +GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate_wound_static_data()) + +/// Constructs [GLOB.all_wound_pregen_data] by iterating through a typecache of pregen data, ignoring abstract types, and instantiating the rest. +/proc/generate_wound_static_data() + RETURN_TYPE(/list/datum/wound_pregen_data) + + var/list/datum/wound_pregen_data/all_pregen_data = list() + + for (var/datum/wound_pregen_data/iterated_path as anything in typecacheof(path = /datum/wound_pregen_data, ignore_root_path = TRUE)) + if (initial(iterated_path.abstract)) + continue + + if (!isnull(all_pregen_data[initial(iterated_path.wound_path_to_generate)])) + stack_trace("pre-existing pregen data for [initial(iterated_path.wound_path_to_generate)] when [iterated_path] was being considered: [all_pregen_data[initial(iterated_path.wound_path_to_generate)]]. \ + this is definitely a bug, and is probably because one of the two pregen data have the wrong wound typepath defined. [iterated_path] will not be instantiated") + + continue + + var/datum/wound_pregen_data/pregen_data = new iterated_path + all_pregen_data[pregen_data.wound_path_to_generate] = pregen_data + + return all_pregen_data + +// A wound series "collection" is merely a way for us to track what is in what series, and what their types are. +// Without this, we have no centralized way to determine what type is in what series outside of iterating over every pregen data. + +/// A branching assoc list of (series -> list(severity -> list(typepath -> weight))). Allows you to say "I want a generic slash wound", +/// then "Of severity 2", and get a wound of that description - via get_corresponding_wound_type() +/// Series: A generic wound_series, such as WOUND_SERIES_BONE_BLUNT_BASIC +/// Severity: Any wounds held within this will be of this severity. +/// Typepath, Weight: Merely a pairing of a given typepath to its weight, held for convenience in pickweight. +GLOBAL_LIST_INIT(wound_series_collections, generate_wound_series_collection()) + +// Series -> severity -> type -> weight +/// Generates [wound_series_collections] by iterating through all pregen_data. Refer to the mentioned list for documentation +/proc/generate_wound_series_collection() + RETURN_TYPE(/list/datum/wound) + + var/list/datum/wound/wound_collection = list() + + for (var/datum/wound/wound_typepath as anything in typecacheof(/datum/wound, FALSE, TRUE)) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_typepath] + if (!pregen_data) + continue + + if (pregen_data.abstract) + stack_trace("somehow, a abstract wound_pregen_data instance ([pregen_data.type]) was instantiated and made it to generate_wound_series_collection()! \ + i literally have no idea how! please fix this!") + continue + + var/series = pregen_data.wound_series + var/list/datum/wound/series_list = wound_collection[series] + if (isnull(series_list)) + wound_collection[series] = list() + series_list = wound_collection[series] + + var/severity = "[(initial(wound_typepath.severity))]" + var/list/datum/wound/severity_list = series_list[severity] + if (isnull(severity_list)) + series_list[severity] = list() + severity_list = series_list[severity] + + severity_list[wound_typepath] = pregen_data.weight + + return wound_collection + +/// A branching assoc list of (wounding_type -> list(wound_series)). +/// Allows for determining of which wound series are caused by what. +GLOBAL_LIST_INIT(wounding_types_to_series, list( + WOUND_BLUNT = list( + WOUND_SERIES_BONE_BLUNT_BASIC + ), + WOUND_SLASH = list( + WOUND_SERIES_FLESH_SLASH_BLEED, + ), + WOUND_BURN = list( + WOUND_SERIES_FLESH_BURN_BASIC, + ), + WOUND_PUNCTURE = list( + WOUND_SERIES_FLESH_PUNCTURE_BLEED + ), +)) + +/// Used in get_corresponding_wound_type(): Will pick the highest severity wound out of severity_min and severity_max +#define WOUND_PICK_HIGHEST_SEVERITY 1 +/// Used in get_corresponding_wound_type(): Will pick the lowest severity wound out of severity_min and severity_max +#define WOUND_PICK_LOWEST_SEVERITY 2 + +/** + * Searches through all wounds for any of proper type, series, and biostate, and then returns a single one via pickweight. + * Is able to discern between, say, a flesh slash wound, and a metallic slash wound, and will return the respective one for the provided limb. + * + * The severity_max and severity_pick_mode args mostly exist in case you want a wound in a series that may not have your ideal severity wound, as it lets you + * essentially set a "fallback", where if your ideal wound doesnt exist, it'll still return something, trying to get closest to your ideal severity. + * + * Generally speaking, if you want a critical/severe/moderate wound, you should set severity_min to WOUND_SEVERITY_MODERATE, severity_max to your ideal wound, + * and severity_pick_mode to WOUND_PICK_HIGHEST_SEVERITY - UNLESS you for some reason want the LOWEST severity, in which case you should set + * severity_max to the highest wound you're willing to tolerate, and severity_pick_mode to WOUND_PICK_LOWEST_SEVERITY. + * + * Args: + * * list/wounding_types: A list of wounding_types. Only wounds that accept these wound types will be considered. + * * obj/item/bodypart/part: The limb we are considering. Extremely important for biostates. + * * severity_min: The minimum wound severity we will search for. + * * severity_max = severity_min: The maximum wound severity we will search for. + * * severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY: The "pick mode" we will use when considering multiple wounds of acceptable severity. See the above defines. + * * random_roll = TRUE: If this is considered a "random" consideration. If true, only wounds that can be randomly generated will be considered. + * * duplicates_allowed = FALSE: If exact duplicates of a given wound on part are tolerated. Useful for simply getting a path and not instantiating. + * * care_about_existing_wounds = TRUE: If we iterate over wounds to see if any are above or at a given wounds severity, and disregard it if any are. Useful for simply getting a path and not instantiating. + * + * Returns: + * A randomly picked wound typepath meeting all the above criteria and being applicable to the part's biotype - or null if there were none. + */ +/proc/get_corresponding_wound_type(list/wounding_types, obj/item/bodypart/part, severity_min, severity_max = severity_min, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, random_roll = TRUE, duplicates_allowed = FALSE, care_about_existing_wounds = TRUE) + RETURN_TYPE(/datum/wound) // note that just because its set to return this doesnt mean its non-nullable + + var/list/wounding_type_list = list() + for (var/wounding_type as anything in wounding_types) + wounding_type_list += GLOB.wounding_types_to_series[wounding_type] + if (!length(wounding_type_list)) + return null + + var/list/datum/wound/paths_to_pick_from = list() + for (var/series as anything in shuffle(wounding_type_list)) + var/list/severity_list = GLOB.wound_series_collections[series] + if (!length(severity_list)) + continue + + var/picked_severity + for (var/severity_text as anything in shuffle(GLOB.wound_severities_chronological)) + var/severity = text2num(severity_text) + if (severity > severity_min || severity < severity_max) + continue + + if (isnull(picked_severity) || ((severity_pick_mode == WOUND_PICK_HIGHEST_SEVERITY && severity > picked_severity) || (severity_pick_mode == WOUND_PICK_LOWEST_SEVERITY && severity < picked_severity))) + picked_severity = severity + + var/list/wound_typepaths = severity_list["[picked_severity]"] + if (!length(wound_typepaths)) + continue + + for (var/datum/wound/iterated_path as anything in wound_typepaths) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_path] + if (pregen_data.can_be_applied_to(part, wounding_types, random_roll = random_roll, duplicates_allowed = duplicates_allowed, care_about_existing_wounds = care_about_existing_wounds)) + paths_to_pick_from[iterated_path] = wound_typepaths[iterated_path] + + return pick_weight(paths_to_pick_from) // we found our winners! + +/// Assoc list of biotype -> ideal scar file to be used and grab stuff from. +GLOBAL_LIST_INIT(biotypes_to_scar_file, list( + "[BIO_FLESH]" = FLESH_SCAR_FILE, + "[BIO_BONE]" = BONE_SCAR_FILE +)) // ~burn wound infection defines // Thresholds for infection for burn wounds, once infestation hits each threshold, things get steadily worse @@ -125,23 +306,25 @@ GLOBAL_LIST_INIT(bio_state_states, list( // ~mangling defines -// With the wounds pt. 2 update, general dismemberment now requires 2 things for a limb to be dismemberable (bone only creatures just need the second): -// 1. Flesh is mangled: A critical slash or pierce wound on that limb -// 2. Bone is mangled: At least a severe bone wound on that limb -// see [/obj/item/bodypart/proc/get_mangled_state] for more information +// With the wounds pt. 2 update, general dismemberment now requires 2 things for a limb to be dismemberable (exterior/bone only creatures just need the second): +// 1. Exterior is mangled: A critical slash or pierce wound on that limb +// 2. Interior is mangled: At least a severe bone wound on that limb +// Lack of exterior or interior count as mangled exterior/interior respectively +// see [/obj/item/bodypart/proc/get_mangled_state] for more information, as well as GLOB.bio_state_anatomy #define BODYPART_MANGLED_NONE NONE -#define BODYPART_MANGLED_BONE (1<<0) -#define BODYPART_MANGLED_FLESH (1<<1) -#define BODYPART_MANGLED_BOTH (BODYPART_MANGLED_BONE | BODYPART_MANGLED_FLESH) +#define BODYPART_MANGLED_INTERIOR (1<<0) +#define BODYPART_MANGLED_EXTERIOR (1<<1) +#define BODYPART_MANGLED_BOTH (BODYPART_MANGLED_INTERIOR | BODYPART_MANGLED_EXTERIOR) // ~wound flag defines -/// If having this wound counts as mangled flesh for dismemberment -#define MANGLES_FLESH (1<<0) -/// If having this wound counts as mangled bone for dismemberment -#define MANGLES_BONE (1<<1) +/// If having this wound counts as mangled exterior for dismemberment +#define MANGLES_EXTERIOR (1<<0) +/// If having this wound counts as mangled interior for dismemberment +#define MANGLES_INTERIOR (1<<1) /// If this wound marks the limb as being allowed to have gauze applied #define ACCEPTS_GAUZE (1<<2) - +/// If this wound allows the victim to grasp it +#define CAN_BE_GRASPED (1<<3) // ~scar persistence defines // The following are the order placements for persistent scar save formats diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index 3a360c012f6..0ed00e7dd94 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -91,11 +91,8 @@ log_combat(user, H, "wounded via throat slitting", source) H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD) - if(slit_throat) - var/datum/wound/slash/flesh/critical/screaming_through_a_slit_throat = new - if (!screaming_through_a_slit_throat.apply_wound(slit_throat, wound_source = "throat slit")) - qdel(screaming_through_a_slit_throat) - H.apply_status_effect(/datum/status_effect/neck_slice) + if (H.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL)) + H.apply_status_effect(/datum/status_effect/neck_slice) /** * Handles a user butchering a target diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm index 433784e845e..eeb3d75461b 100644 --- a/code/datums/components/embedded.dm +++ b/code/datums/components/embedded.dm @@ -93,7 +93,8 @@ if(harmful) victim.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject) playsound(victim,'sound/weapons/bladeslice.ogg', 40) - weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! + if (limb.can_bleed()) + weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! damage += weapon.w_class * impact_pain_mult victim.add_mood_event("embedded", /datum/mood_event/embedded) @@ -303,7 +304,7 @@ return var/damage = weapon.w_class * remove_pain_mult limb.receive_damage(brute=(1-pain_stam_pct) * damage * 1.5, sharpness=SHARP_EDGED) // Performs exit wounds and flings the user to the caster if nearby - limb.force_wound_upwards(/datum/wound/pierce/bleed/moderate) + victim.cause_wound_of_type_and_severity(WOUND_PIERCE, limb, WOUND_SEVERITY_MODERATE) victim.adjustStaminaLoss(pain_stam_pct * damage) playsound(get_turf(victim), 'sound/effects/wounds/blood2.ogg', 50, TRUE) diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index 0604ec2fd12..a3a590a989c 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -307,7 +307,7 @@ var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] - var/wound_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling + var/wounding_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling wound_info_by_part -= hit_part // technically this only checks armor worn the moment that all the pellets resolve rather than as each one hits you, @@ -320,7 +320,7 @@ armor_factor *= ARMOR_WEAKENED_MULTIPLIER damage_dealt *= max(0, 1 - armor_factor*0.01) - hit_part.painless_wound_roll(wound_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness)) + hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness)) var/limb_hit_text = "" if(hit_part) diff --git a/code/datums/elements/kneecapping.dm b/code/datums/elements/kneecapping.dm index 217c11d4361..340938c430c 100644 --- a/code/datums/elements/kneecapping.dm +++ b/code/datums/elements/kneecapping.dm @@ -80,9 +80,11 @@ if(do_after(attacker, 3 SECONDS, target, interaction_key = weapon)) attacker.visible_message(span_warning("[attacker] swings [attacker.p_their()] [weapon] at [target]'s kneecaps!"), span_danger("You swing \the [weapon] at [target]'s kneecaps!")) - var/datum/wound/blunt/bone/severe/severe_wound_type = /datum/wound/blunt/bone/severe - var/datum/wound/blunt/bone/critical/critical_wound_type = /datum/wound/blunt/bone/critical - leg.receive_damage(brute = weapon.force, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10), damage_source = "kneecapping") + + var/min_wound = leg.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = weapon) + var/max_wound = leg.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = weapon) + + leg.receive_damage(brute = weapon.force, wound_bonus = rand(min_wound, max_wound + 10), damage_source = "kneecapping") target.emote("scream") log_combat(attacker, target, "broke the kneecaps of", weapon) target.update_damage_overlays() diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm index 8322d09e496..ba04fc07c54 100644 --- a/code/datums/mutations/hulk.dm +++ b/code/datums/mutations/hulk.dm @@ -63,13 +63,19 @@ *arg1 is the arm to evaluate damage of and possibly break. */ /datum/mutation/human/hulk/proc/break_an_arm(obj/item/bodypart/arm) + var/severity switch(arm.brute_dam) if(45 to 50) - arm.force_wound_upwards(/datum/wound/blunt/bone/critical, wound_source = "hulk smashing") + severity = WOUND_SEVERITY_CRITICAL if(41 to 45) - arm.force_wound_upwards(/datum/wound/blunt/bone/severe, wound_source = "hulk smashing") + severity = WOUND_SEVERITY_SEVERE if(35 to 41) - arm.force_wound_upwards(/datum/wound/blunt/bone/moderate, wound_source = "hulk smashing") + severity = WOUND_SEVERITY_MODERATE + + if (isnull(severity)) + return + + owner.cause_wound_of_type_and_severity(WOUND_BLUNT, arm, severity, wound_source = "hulk smashing") /datum/mutation/human/hulk/on_life(seconds_per_tick, times_fired) if(owner.health < owner.crit_threshold) diff --git a/code/datums/status_effects/debuffs/choke.dm b/code/datums/status_effects/debuffs/choke.dm index f626cb52be5..253c471150d 100644 --- a/code/datums/status_effects/debuffs/choke.dm +++ b/code/datums/status_effects/debuffs/choke.dm @@ -215,8 +215,8 @@ if(iscarbon(victim)) var/mob/living/carbon/carbon_victim = victim var/obj/item/bodypart/chest = carbon_victim.get_bodypart(BODY_ZONE_CHEST) - if(chest) - chest.force_wound_upwards(/datum/wound/blunt/bone/severe, wound_source = "human force to the chest") + carbon_victim.cause_wound_of_type_and_severity(WOUND_BLUNT, chest, WOUND_SEVERITY_SEVERE, wound_source = "human force to the chest") + playsound(owner, 'sound/creatures/crack_vomit.ogg', 120, extrarange = 5, falloff_exponent = 4) vomit_up() diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index c9f02f48f0b..95064d04777 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -408,7 +408,9 @@ var/still_bleeding = FALSE for(var/datum/wound/bleeding_thing as anything in throat.wounds) - if(bleeding_thing.wound_type == WOUND_SLASH && bleeding_thing.severity > WOUND_SEVERITY_MODERATE) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[bleeding_thing.type] + + if(pregen_data.wounding_types_valid(list(WOUND_SLASH)) && bleeding_thing.severity > WOUND_SEVERITY_MODERATE && bleeding_thing.blood_flow > 0) still_bleeding = TRUE break if(!still_bleeding) diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm index 5da74ef7722..6ec793c5672 100644 --- a/code/datums/status_effects/wound_effects.dm +++ b/code/datums/status_effects/wound_effects.dm @@ -54,11 +54,11 @@ right = C.get_bodypart(BODY_ZONE_R_LEG) update_limp() RegisterSignal(C, COMSIG_MOVABLE_MOVED, PROC_REF(check_step)) - RegisterSignals(C, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB), PROC_REF(update_limp)) + RegisterSignals(C, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_POST_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB), PROC_REF(update_limp)) return TRUE /datum/status_effect/limp/on_remove() - UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB)) + UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_POST_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB)) /atom/movable/screen/alert/status_effect/limp name = "Limping" @@ -165,37 +165,25 @@ if(W == linked_wound) qdel(src) - -// bones -/datum/status_effect/wound/blunt/bone - -/datum/status_effect/wound/blunt/bone/on_apply() - . = ..() - RegisterSignal(owner, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands)) - on_swap_hands() - -/datum/status_effect/wound/blunt/bone/on_remove() - . = ..() - UnregisterSignal(owner, COMSIG_MOB_SWAP_HANDS) - var/mob/living/carbon/wound_owner = owner - wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound) - -/datum/status_effect/wound/blunt/bone/proc/on_swap_hands() - SIGNAL_HANDLER - - var/mob/living/carbon/wound_owner = owner - if(wound_owner.get_active_hand() == linked_limb) - wound_owner.add_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound, (linked_wound.interaction_efficiency_penalty - 1)) - else - wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/blunt_wound) - -/datum/status_effect/wound/blunt/bone/nextmove_modifier() +/datum/status_effect/wound/nextmove_modifier() var/mob/living/carbon/C = owner if(C.get_active_hand() == linked_limb) - return linked_wound.interaction_efficiency_penalty + return linked_wound.get_action_delay_mult() - return 1 + return ..() + +/datum/status_effect/wound/nextmove_adjust() + var/mob/living/carbon/C = owner + + if(C.get_active_hand() == linked_limb) + return linked_wound.get_action_delay_increment() + + return ..() + + +// bones +/datum/status_effect/wound/blunt/bone // blunt /datum/status_effect/wound/blunt/bone/moderate diff --git a/code/datums/wounds/_wound_static_data.dm b/code/datums/wounds/_wound_static_data.dm index 4d28e80a910..7a59ea57413 100644 --- a/code/datums/wounds/_wound_static_data.dm +++ b/code/datums/wounds/_wound_static_data.dm @@ -2,28 +2,6 @@ // For example: You can make a pregen_data subtype for your wound that overrides can_be_applied_to to only apply to specifically slimeperson limbs. // Without this, youre stuck with very static initial variables. -GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate_wound_static_data()) - -/proc/generate_wound_static_data() - RETURN_TYPE(/list/datum/wound_pregen_data) - - var/list/datum/wound_pregen_data/data = list() - - for (var/datum/wound_pregen_data/path as anything in typecacheof(path = /datum/wound_pregen_data, ignore_root_path = TRUE)) - if (initial(path.abstract)) - continue - - if (!isnull(data[initial(path.wound_path_to_generate)])) - stack_trace("pre-existing pregen data for [initial(path.wound_path_to_generate)] when [path] was being considered: [data[initial(path.wound_path_to_generate)]]. \ - this is definitely a bug, and is probably because one of the two pregen data have the wrong wound typepath defined. [path] will not be instantiated") - - continue - - var/datum/wound_pregen_data/pregen_data = new path - data[pregen_data.wound_path_to_generate] = pregen_data - - return data - /// A singleton datum that holds pre-gen and static data about a wound. Each wound datum should have a corresponding wound_pregen_data. /datum/wound_pregen_data /// The typepath of the wound we will be handling and storing data of. NECESSARY IF THIS IS A NON-ABSTRACT TYPE! @@ -38,7 +16,7 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate /// A list of biostates a limb must have to receive our wound, in wounds.dm. var/required_limb_biostate /// If false, we will check if the limb has all of our required biostates instead of just any. - var/check_for_any = FALSE + var/require_any_biostate = FALSE /// If false, we will iterate through wounds on a given limb, and if any match our type, we wont add our wound. var/duplicates_allowed = FALSE @@ -48,6 +26,30 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate /// A list of bodyzones we are applicable to. var/list/viable_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + /// The types of attack that can generate this wound. E.g. WOUND_SLASH = A sharp attack can cause this, WOUND_BLUNT = an attack with no sharpness/an attack with sharpness against a limb with mangled exterior can cause this. + var/list/required_wounding_types + /// If true, this wound can only be generated by all [required_wounding_types] at once, not just any. + var/match_all_wounding_types = FALSE + + /// The weight that will be used if, by the end of wound selection, there are multiple valid wounds. This will be inserted into pick_weight, so use integers. + var/weight = WOUND_DEFAULT_WEIGHT + + /// The minimum injury roll a attack must get to generate us. Affected by our wound's threshold_penalty and series_threshold_penalty, as well as the attack's wound_bonus. See check_wounding_mods(). + var/threshold_minimum + + /// The series of wounds this is in. See wounds.dm (the defines file) for a more detailed explanation - but tldr is that no 2 wounds of the same series can be on a limb. + var/wound_series + + /// If true, we will attempt to, during a random wound roll, overpower and remove other wound typepaths from the possible wounds list using [competition_mode] and [overpower_wounds_of_even_severity]. + var/compete_for_wounding = TRUE + /// The competition mode with which we will remove other wounds from a possible wound roll assuming [compete_for_wounding] is TRUE. See wounds.dm, the defines file, for more information on what these do. + var/competition_mode = WOUND_COMPETITION_OVERPOWER_LESSERS + /// If this and [compete_for_wounding] is true, we will remove wounds of an even severity to us during a random wound roll. + var/overpower_wounds_of_even_severity = FALSE + + /// A list of BIO_ defines that will be iterated over in order to determine the scar file our wound will generate. + /// Use generate_scar_priorities to create a custom list. + var/list/scar_priorities /datum/wound_pregen_data/New() . = ..() @@ -58,21 +60,33 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate if (wound_path_to_generate == null) stack_trace("wound_path_to_generate null - please set it! occured on: [src.type]") + scar_priorities = generate_scar_priorities() + +/// Should return a list of BIO_ biostate priorities, in order. See [scar_priorities] for further documentation. +/datum/wound_pregen_data/proc/generate_scar_priorities() + RETURN_TYPE(/list) + + var/list/priorities = list( + "[BIO_FLESH]", + "[BIO_BONE]", + ) + + return priorities + // this proc is the primary reason this datum exists - a singleton instance so we can always run this proc even without the wound existing /** * Args: * * obj/item/bodypart/limb: The limb we are considering. - * * wound_type: The type of the "wound acquisition attempt". Example: A slashing attack cannot proc a blunt wound, so wound_type = WOUND_SLASH would - * fail if we expect WOUND_BLUNT. Defaults to the wound type we expect. + * * list/suggested_wounding_types: The wounding types to be checked against the wounding types we require. Defaults to required_wounding_types. * * datum/wound/old_wound: If we would replace a wound, this would be said wound. Nullable. * * random_roll = FALSE: If this is in the context of a random wound generation, and this wound wasn't specifically checked. * * Returns: - * FALSE if the limb cannot be wounded, if wound_type is not ours, if we have a higher severity wound already in our series, + * FALSE if the limb cannot be wounded, if the wounding types dont match ours (via wounding_types_valid()), if we have a higher severity wound already in our series, * if we have a biotype mismatch, if the limb isnt in a viable zone, or if theres any duplicate wound types. * TRUE otherwise. */ -/datum/wound_pregen_data/proc/can_be_applied_to(obj/item/bodypart/limb, wound_type = initial(wound_path_to_generate.wound_type), datum/wound/old_wound, random_roll = FALSE) +/datum/wound_pregen_data/proc/can_be_applied_to(obj/item/bodypart/limb, list/suggested_wounding_types = required_wounding_types, datum/wound/old_wound, random_roll = FALSE, duplicates_allowed = src.duplicates_allowed, care_about_existing_wounds = TRUE) SHOULD_BE_PURE(TRUE) if (!istype(limb) || !limb.owner) @@ -84,11 +98,13 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate if (HAS_TRAIT(limb.owner, TRAIT_NEVER_WOUNDED) || (limb.owner.status_flags & GODMODE)) return FALSE - if (wound_type != initial(wound_path_to_generate.wound_type)) - return - else + if (!wounding_types_valid(suggested_wounding_types)) + return FALSE + + if (care_about_existing_wounds) for (var/datum/wound/preexisting_wound as anything in limb.wounds) - if (preexisting_wound.wound_series == initial(wound_path_to_generate.wound_series)) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[preexisting_wound.type] + if (pregen_data.wound_series == wound_series) if (preexisting_wound.severity >= initial(wound_path_to_generate.severity)) return FALSE @@ -111,7 +127,7 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate /// Returns true if we have the given biostates, or any biostate in it if check_for_any is true. False otherwise. /datum/wound_pregen_data/proc/biostate_valid(biostate) - if (check_for_any) + if (require_any_biostate) if (!(biostate & required_limb_biostate)) return FALSE else if (!((biostate & required_limb_biostate) == required_limb_biostate)) // check for all @@ -119,6 +135,50 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate return TRUE +/** + * A simple getter for [weight], with arguments supplied to allow custom behavior. + * + * Args: + * * obj/item/bodypart/limb: The limb we are contemplating being added to. Nullable. + * * woundtype: The woundtype of the assumed attack that would generate us. Nullable. + * * damage: The raw damage that would cause us. Nullable. + * * attack_direction: The direction of the attack that'd cause us. Nullable. + * * damage_source: The entity that would cause us. Nullable. + * + * Returns: + * Our weight. + */ +/datum/wound_pregen_data/proc/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + return weight + +/// Returns TRUE if we use WOUND_ALL, or we require all types and have all/if we require any and have any, FALSE otherwise. +/datum/wound_pregen_data/proc/wounding_types_valid(list/suggested_wounding_types) + if (WOUND_ALL in required_wounding_types) + return TRUE + if (!length(suggested_wounding_types)) + return FALSE + + for (var/iter_wounding_type as anything in suggested_wounding_types) + if (!(iter_wounding_type in required_wounding_types)) + if (match_all_wounding_types) + return FALSE + else + if (!match_all_wounding_types) + return TRUE + + return match_all_wounding_types // if we get here, we've matched everything + +/** + * A simple getter for [threshold_minimum], with arguments supplied to allow custom behavior. + * + * Args: + * * obj/item/bodypart/part: The limb we are contemplating being added to. + * * attack_direction: The direction of the attack that'd generate us. Nullable. + * * damage_source: The source of the damage that'd cause us. Nullable. + */ +/datum/wound_pregen_data/proc/get_threshold_for(obj/item/bodypart/part, attack_direction, damage_source) + return threshold_minimum + /// Returns a new instance of our wound datum. /datum/wound_pregen_data/proc/generate_instance(obj/item/bodypart/limb, ...) RETURN_TYPE(/datum/wound) @@ -126,10 +186,13 @@ GLOBAL_LIST_INIT_TYPED(all_wound_pregen_data, /datum/wound_pregen_data, generate return new wound_path_to_generate /datum/wound_pregen_data/Destroy(force, ...) - stack_trace("[src], a singleton wound pregen data instance, was destroyed! This should not happen!") + var/error_message = "[src], a singleton wound pregen data instance, was destroyed! This should not happen!" + if (force) + error_message += " NOTE: This Destroy() was called with force == TRUE. This instance will be deleted and replaced with a new one." + stack_trace(error_message) if (!force) - return + return QDEL_HINT_LETMELIVE . = ..() diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index 4d5adb3e517..66caa2a4520 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -16,6 +16,11 @@ #define WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS 15 +// Applied into wounds when they're scanned with the wound analyzer, halves time to treat them manually. +#define TRAIT_WOUND_SCANNED "wound_scanned" +// I dunno lol +#define ANALYZER_TRAIT "analyzer_trait" + /datum/wound /// What it's named var/name = "Wound" @@ -29,8 +34,8 @@ /// If this wound can generate a scar. var/can_scar = TRUE - /// The file we take our scar descriptions from. - var/scar_file + /// The default file we take our scar descriptions from, if we fail to get the ideal file. + var/default_scar_file /// needed for "your arm has a compound fracture" vs "your arm has some third degree burns" var/a_or_from = "a" @@ -43,10 +48,6 @@ /// Either WOUND_SEVERITY_TRIVIAL (meme wounds like stubbed toe), WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, or WOUND_SEVERITY_CRITICAL (or maybe WOUND_SEVERITY_LOSS) var/severity = WOUND_SEVERITY_MODERATE - /// The type of attack that can generate this wound. E.g. WOUND_SLASH = A sword can cause us, or WOUND_BLUNT = a hammer can cause us/a sword attacking mangled flesh. - var/wound_type - /// The series of wounds this is in. See wounds.dm (the defines file) for a more detailed explanation - but tldr is that no 2 wounds of the same series can be on a limb. - var/wound_series /// Who owns the body part that we're wounding var/mob/living/carbon/victim = null @@ -57,8 +58,8 @@ var/list/treatable_by /// Specific items such as bandages or sutures that can try directly treating this wound only if the user has the victim in an aggressive grab or higher var/list/treatable_by_grabbed - /// Tools with the specified tool flag will also be able to try directly treating this wound - var/treatable_tool + /// Any tools with any of the flags in this list will be usable to try directly treating this wound + var/list/treatable_tools /// How long it will take to treat this wound with a standard effective tool, assuming it doesn't need surgery var/base_treat_time = 5 SECONDS @@ -72,13 +73,11 @@ var/limp_chance /// How much we're contributing to this limb's bleed_rate var/blood_flow - /// Essentially, keeps track of whether or not this wound is capable of bleeding (in case the owner has the NOBLOOD species trait) - var/no_bleeding = FALSE - /// The minimum we need to roll on [/obj/item/bodypart/proc/check_wounding] to begin suffering this wound, see check_wounding_mods() for more - var/threshold_minimum /// How much having this wound will add to all future check_wounding() rolls on this limb, to allow progression to worse injuries with repeated damage var/threshold_penalty + /// How much having this wound will add to all future check_wounding() rolls on this limb, but only for wounds of its own series + var/series_threshold_penalty = 0 /// If we need to process each life tick var/processes = FALSE @@ -89,9 +88,12 @@ var/status_effect_type /// If we're operating on this wound and it gets healed, we'll nix the surgery too var/datum/surgery/attached_surgery - /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * 25 power + /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * [base_xadone_progress_to_qdel] power var/cryo_progress + /// The base amount of [cryo_progress] required to have ourselves fully healed by cryo. Multiplied against severity. + var/base_xadone_progress_to_qdel = 33 + /// What kind of scars this wound will create description wise once healed var/scar_keyword = "generic" /// If we've already tried scarring while removing (remove_wound can be called twice in a del chain, let's be nice to our code yeah?) TODO: make this cleaner @@ -102,17 +104,66 @@ /// What flags apply to this wound var/wound_flags = (ACCEPTS_GAUZE) + /// The unique ID of our wound for use with [actionspeed_mod]. Defaults to REF(src). + var/unique_id + /// The actionspeed modifier we will use in case we are on the arms and have a interaction penalty. Qdelled on destroy. + var/datum/actionspeed_modifier/wound_interaction_inefficiency/actionspeed_mod + +/datum/wound/New() + . = ..() + + unique_id = generate_unique_id() + update_actionspeed_modifier() + /datum/wound/Destroy() - if(attached_surgery) - QDEL_NULL(attached_surgery) + QDEL_NULL(attached_surgery) if (limb) remove_wound() + + QDEL_NULL(actionspeed_mod) + return ..() -// Applied into wounds when they're scanned with the wound analyzer, halves time to treat them manually. -#define TRAIT_WOUND_SCANNED "wound_scanned" -// I dunno lol -#define ANALYZER_TRAIT "analyzer_trait" +/// If we should have an actionspeed_mod, ensures we do and updates its slowdown. Otherwise, ensures we dont have one +/// by qdeleting any existing modifier. +/datum/wound/proc/update_actionspeed_modifier() + if (should_have_actionspeed_modifier()) + if (!actionspeed_mod) + generate_actionspeed_modifier() + actionspeed_mod.multiplicative_slowdown = get_effective_actionspeed_modifier() + victim?.update_actionspeed() + else + remove_actionspeed_modifier() + +/// Returns TRUE if we have an interaction_efficiency_penalty, and if we are on the arms, FALSE otherwise. +/datum/wound/proc/should_have_actionspeed_modifier() + return (limb && victim && (limb.body_zone == BODY_ZONE_L_ARM || limb.body_zone == BODY_ZONE_R_ARM) && interaction_efficiency_penalty != 0) + +/// If we have no actionspeed_mod, generates a new one with our unique ID, sets actionspeed_mod to it, then returns it. +/datum/wound/proc/generate_actionspeed_modifier() + RETURN_TYPE(/datum/actionspeed_modifier) + + if (actionspeed_mod) + return actionspeed_mod + + var/datum/actionspeed_modifier/wound_interaction_inefficiency/new_modifier = new /datum/actionspeed_modifier/wound_interaction_inefficiency(unique_id, src) + new_modifier.multiplicative_slowdown = get_effective_actionspeed_modifier() + victim?.add_actionspeed_modifier(new_modifier) + + actionspeed_mod = new_modifier + return actionspeed_mod + +/// If we have an actionspeed_mod, qdels it and sets our ref of it to null. +/datum/wound/proc/remove_actionspeed_modifier() + if (!actionspeed_mod) + return + + victim?.remove_actionspeed_modifier(actionspeed_mod) + QDEL_NULL(actionspeed_mod) + +/// Generates the ID we use for [unique_id], which is also set as our actionspeed mod's ID +/datum/wound/proc/generate_unique_id() + return REF(src) // unique, cannot change, a perfect id /** * apply_wound() is used once a wound type is instantiated to assign it to a bodypart, and actually come into play. @@ -142,7 +193,6 @@ set_limb(L) LAZYADD(victim.all_wounds, src) LAZYADD(limb.wounds, src) - no_bleeding = HAS_TRAIT(victim, TRAIT_NOBLOOD) update_descriptions() limb.update_wounds() if(status_effect_type) @@ -162,7 +212,7 @@ var/msg = span_danger("[victim]'s [limb.plaintext_zone] [occur_text]!") var/vis_dist = COMBAT_MESSAGE_RANGE - if(severity != WOUND_SEVERITY_MODERATE) + if(severity > WOUND_SEVERITY_MODERATE) msg = "[msg]" vis_dist = DEFAULT_MESSAGE_RANGE @@ -183,7 +233,7 @@ // We assume we aren't being randomly applied - we have no reason to believe we are // And, besides, if we were, you could just as easily check our pregen data rather than run this proc // Generally speaking this proc is called in apply_wound, which is called when the caller is already confidant in its ability to be applied - return pregen_data.can_be_applied_to(L, wound_type, old_wound) + return pregen_data.can_be_applied_to(L, old_wound = old_wound) /// Returns the zones we can be applied to. /datum/wound/proc/get_viable_zones() @@ -205,13 +255,66 @@ SIGNAL_HANDLER set_victim(null) +/// Setter for [victim]. Should completely transfer signals, attributes, etc. To the new victim - if there is any, as it can be null. /datum/wound/proc/set_victim(new_victim) if(victim) + UnregisterSignal(victim, list(COMSIG_QDELETING, COMSIG_MOB_SWAP_HANDS, COMSIG_CARBON_POST_REMOVE_LIMB, COMSIG_CARBON_POST_ATTACH_LIMB)) UnregisterSignal(victim, COMSIG_QDELETING) + UnregisterSignal(victim, COMSIG_MOB_SWAP_HANDS) + UnregisterSignal(victim, COMSIG_CARBON_POST_REMOVE_LIMB) + if (actionspeed_mod) + victim.remove_actionspeed_modifier(actionspeed_mod) // no need to qdelete it, just remove it from our victim + remove_wound_from_victim() victim = new_victim if(victim) RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(null_victim)) + RegisterSignals(victim, list(COMSIG_MOB_SWAP_HANDS, COMSIG_CARBON_POST_REMOVE_LIMB, COMSIG_CARBON_POST_ATTACH_LIMB), PROC_REF(add_or_remove_actionspeed_mod)) + + if (limb) + start_limping_if_we_should() // the status effect already handles removing itself + add_or_remove_actionspeed_mod() + +/// Proc called to change the variable `limb` and react to the event. +/datum/wound/proc/set_limb(obj/item/bodypart/new_value, replaced = FALSE) + if(limb == new_value) + return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made. + . = limb + if(limb) // if we're nulling limb, we're basically detaching from it, so we should remove ourselves in that case + UnregisterSignal(limb, COMSIG_QDELETING) + UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED)) + LAZYREMOVE(limb.wounds, src) + limb.update_wounds(replaced) + if (disabling) + limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) + + limb = new_value + + // POST-CHANGE + + if (limb) + RegisterSignal(limb, COMSIG_QDELETING, PROC_REF(source_died)) + RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), PROC_REF(gauze_state_changed)) + if (disabling) + limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) + + if (victim) + start_limping_if_we_should() // the status effect already handles removing itself + add_or_remove_actionspeed_mod() + + update_inefficiencies() + +/datum/wound/proc/add_or_remove_actionspeed_mod() + update_actionspeed_modifier() + if (actionspeed_mod) + if(victim.get_active_hand() == limb) + victim.add_actionspeed_modifier(actionspeed_mod, TRUE) + else + victim.remove_actionspeed_modifier(actionspeed_mod) + +/datum/wound/proc/start_limping_if_we_should() + if ((limb.body_zone == BODY_ZONE_L_LEG || limb.body_zone == BODY_ZONE_R_LEG) && limp_slowdown > 0 && limp_chance > 0) + victim.apply_status_effect(/datum/status_effect/limp) /datum/wound/proc/source_died() SIGNAL_HANDLER @@ -220,18 +323,27 @@ /// 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 /datum/wound/proc/remove_wound(ignore_limb, replaced = FALSE) //TODO: have better way to tell if we're getting removed without replacement (full heal) scar stuff + var/old_victim = victim + var/old_limb = limb + set_disabling(FALSE) if(limb && can_scar && !already_scarred && !replaced) already_scarred = TRUE var/datum/scar/new_scar = new new_scar.generate(limb, src) + remove_actionspeed_modifier() + null_victim() // we use the proc here because some behaviors may depend on changing victim to some new value if(limb && !ignore_limb) set_limb(null, replaced) // since we're removing limb's ref to us, we should do the same // if you want to keep the ref, do it externally, theres no reason for us to remember it + if (ismob(old_victim)) + var/mob/mob_victim = old_victim + SEND_SIGNAL(mob_victim, COMSIG_CARBON_POST_LOSE_WOUND, src, old_limb, ignore_limb, replaced) + /datum/wound/proc/remove_wound_from_victim() if(!victim) return @@ -259,28 +371,6 @@ /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. -/datum/wound/proc/set_limb(obj/item/bodypart/new_value, replaced = FALSE) - if(limb == new_value) - return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made. - . = limb - if(limb) // if we're nulling limb, we're basically detaching from it, so we should remove ourselves in that case - UnregisterSignal(limb, COMSIG_QDELETING) - LAZYREMOVE(limb.wounds, src) - limb.update_wounds(replaced) - if (disabling) - limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) - - limb = new_value - - // POST-CHANGE - - if (limb) - RegisterSignal(limb, COMSIG_QDELETING, PROC_REF(source_died)) - if(limb) - if(disabling) - limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) - /// Proc called to change the variable `disabling` and react to the event. /datum/wound/proc/set_disabling(new_value) if(disabling == new_value) @@ -295,6 +385,60 @@ if(limb?.can_be_disabled) limb.update_disabled() +/// Setter for [interaction_efficiency_penalty]. Updates the actionspeed of our actionspeed mod. +/datum/wound/proc/set_interaction_efficiency_penalty(new_value) + var/should_update = (new_value != interaction_efficiency_penalty) + + interaction_efficiency_penalty = new_value + + if (should_update) + update_actionspeed_modifier() + +/// Returns a "adjusted" interaction_efficiency_penalty that will be used for the actionspeed mod. +/datum/wound/proc/get_effective_actionspeed_modifier() + return interaction_efficiency_penalty - 1 + +/// Returns the decisecond multiplier of any click interactions, assuming our limb is being used. +/datum/wound/proc/get_action_delay_mult() + SHOULD_BE_PURE(TRUE) + + return interaction_efficiency_penalty + +/// Returns the decisecond increment of any click interactions, assuming our limb is being used. +/datum/wound/proc/get_action_delay_increment() + SHOULD_BE_PURE(TRUE) + + return 0 + +/// Signal proc for if gauze has been applied or removed from our limb. +/datum/wound/proc/gauze_state_changed() + SIGNAL_HANDLER + + if (wound_flags & ACCEPTS_GAUZE) + update_inefficiencies() + +/// Updates our limping and interaction penalties in accordance with our gauze. +/datum/wound/proc/update_inefficiencies() + if (wound_flags & ACCEPTS_GAUZE) + if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(limb.current_gauze?.splint_factor) + limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor + limp_chance = initial(limp_chance) * limb.current_gauze.splint_factor + else + limp_slowdown = initial(limp_slowdown) + limp_chance = initial(limp_chance) + else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + if(limb.current_gauze?.splint_factor) + set_interaction_efficiency_penalty(1 + ((get_effective_actionspeed_modifier()) * limb.current_gauze.splint_factor)) + else + set_interaction_efficiency_penalty(initial(interaction_efficiency_penalty)) + + if(initial(disabling)) + set_disabling(!limb.current_gauze) + + limb.update_wounds() + + start_limping_if_we_should() /// Additional beneficial effects when the wound is gained, in case you want to give a temporary boost to allow the victim to try an escape or last stand /datum/wound/proc/second_wind() @@ -349,9 +493,9 @@ /// Returns TRUE if the item can be used to treat our wounds. Hooks into treat() - only things that return TRUE here may be used there. /datum/wound/proc/item_can_treat(obj/item/potential_treater, mob/user) // check if we have a valid treatable tool - if(potential_treater.tool_behaviour == treatable_tool) + if(potential_treater.tool_behaviour in treatable_tools) return TRUE - if(treatable_tool == TOOL_CAUTERY && potential_treater.get_temperature() && user == victim) // allow improvised cauterization on yourself without an aggro grab + if(TOOL_CAUTERY in treatable_tools && potential_treater.get_temperature() && user == victim) // allow improvised cauterization on yourself without an aggro grab return TRUE // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(potential_treater, user)) @@ -388,9 +532,20 @@ /// 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 /datum/wound/proc/on_xadone(power) cryo_progress += power - if(cryo_progress > 33 * severity) + + return handle_xadone_progress() + +/// Does various actions based on [cryo_progress]. By default, qdeletes the wound past a certain threshold. +/datum/wound/proc/handle_xadone_progress() + if(cryo_progress > get_xadone_progress_to_qdel()) qdel(src) +/// Returns the amount of [cryo_progress] we need to be qdeleted. +/datum/wound/proc/get_xadone_progress_to_qdel() + SHOULD_BE_PURE(TRUE) + + return base_xadone_progress_to_qdel * severity + /// When synthflesh is applied to the victim, we call this. No sense in setting up an entire chem reaction system for wounds when we only care for a few chems. Probably will change in the future /datum/wound/proc/on_synthflesh(power) return @@ -499,7 +654,6 @@ if(WOUND_SEVERITY_CRITICAL) return "Critical" - /// Returns TRUE if our limb is the head or chest, FALSE otherwise. /// Essential in the sense of "we cannot live without it". /datum/wound/proc/limb_essential() @@ -511,7 +665,17 @@ /// Getter proc for our scar_file, in case we might have some custom scar gen logic. /datum/wound/proc/get_scar_file(obj/item/bodypart/scarred_limb, add_to_scars) - return scar_file + var/datum/wound_pregen_data/pregen_data = get_pregen_data() + // basically we iterate over biotypes until we find the one we want + // fleshy burns will look for flesh then bone + // dislocations will look for flesh, then bone, then metal + var/file = default_scar_file + for (var/biotype as anything in pregen_data.scar_priorities) + if (scarred_limb.biological_state & text2num(biotype)) + file = GLOB.biotypes_to_scar_file[biotype] + break + + return file /// Returns what string is displayed when a limb that has sustained this wound is examined /// (This is examining the LIMB ITSELF, when it's not attached to someone.) @@ -522,7 +686,17 @@ /datum/wound/proc/get_dismember_chance_bonus(existing_chance) SHOULD_BE_PURE(TRUE) - if (wound_type == WOUND_BLUNT && severity >= WOUND_SEVERITY_CRITICAL) + var/datum/wound_pregen_data/pregen_data = get_pregen_data() + + if (WOUND_BLUNT in pregen_data.required_wounding_types && severity >= WOUND_SEVERITY_CRITICAL) return WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS // we only require mangled bone (T2 blunt), but if there's a critical blunt, we'll add 15% more +/// Returns our pregen data, which is practically guaranteed to exist, so this proc can safely be used raw. +/// In fact, since it's RETURN_TYPEd to wound_pregen_data, you can even directly access the variables without having to store the value of this proc in a typed variable. +/// Ex. get_pregen_data().wound_series +/datum/wound/proc/get_pregen_data() + RETURN_TYPE(/datum/wound_pregen_data) + + return GLOB.all_wound_pregen_data[type] + #undef WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS diff --git a/code/datums/wounds/blunt.dm b/code/datums/wounds/blunt.dm index 8a9c34e163d..219b7dd8805 100644 --- a/code/datums/wounds/blunt.dm +++ b/code/datums/wounds/blunt.dm @@ -1,4 +1,3 @@ /datum/wound/blunt name = "Blunt Wound" sound_effect = 'sound/effects/wounds/crack1.ogg' - wound_type = WOUND_BLUNT diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 82c6005ac42..6a9d1e237f3 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -8,11 +8,15 @@ abstract = TRUE required_limb_biostate = BIO_BONE + required_wounding_types = list(WOUND_BLUNT) + + wound_series = WOUND_SERIES_BONE_BLUNT_BASIC + /datum/wound/blunt/bone name = "Blunt (Bone) Wound" wound_flags = (ACCEPTS_GAUZE) - scar_file = BONE_SCAR_FILE + default_scar_file = BONE_SCAR_FILE /// Have we been bone gel'd? var/gelled @@ -33,8 +37,6 @@ /// If this is a chest wound and this is set, we have this chance to cough up blood when hit in the chest var/internal_bleeding_chance = 0 - wound_series = WOUND_SERIES_BONE_BLUNT_BASIC - /* Overwriting of base procs */ @@ -65,14 +67,6 @@ return ..() -/datum/wound/blunt/bone/set_limb(obj/item/bodypart/new_value) - if (limb) - UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED)) - if (new_value) - RegisterSignals(new_value, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), PROC_REF(update_inefficiencies)) - - return ..() - /datum/wound/blunt/bone/remove_wound(ignore_limb, replaced) limp_slowdown = 0 limp_chance = 0 @@ -180,28 +174,6 @@ New common procs for /datum/wound/blunt/bone/ */ -/datum/wound/blunt/bone/proc/update_inefficiencies() - SIGNAL_HANDLER - - if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(limb.current_gauze?.splint_factor) - limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor - limp_chance = initial(limp_chance) * limb.current_gauze.splint_factor - else - limp_slowdown = initial(limp_slowdown) - limp_chance = initial(limp_chance) - victim.apply_status_effect(/datum/status_effect/limp) - else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) - if(limb.current_gauze?.splint_factor) - interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_gauze.splint_factor) - else - interaction_efficiency_penalty = initial(interaction_efficiency_penalty) - - if(initial(disabling)) - set_disabling(!limb.current_gauze) - - limb.update_wounds() - /datum/wound/blunt/bone/get_scar_file(obj/item/bodypart/scarred_limb, add_to_scars) if (scarred_limb.biological_state & BIO_BONE && (!(scarred_limb.biological_state & BIO_FLESH))) // only bone return BONE_SCAR_FILE @@ -221,11 +193,10 @@ interaction_efficiency_penalty = 1.3 limp_slowdown = 3 limp_chance = 50 - threshold_minimum = 35 threshold_penalty = 15 - treatable_tool = TOOL_BONESET + treatable_tools = list(TOOL_BONESET) status_effect_type = /datum/status_effect/wound/blunt/bone/moderate - scar_keyword = "bluntmoderate" + scar_keyword = "dislocate" /datum/wound_pregen_data/bone/dislocate abstract = FALSE @@ -234,6 +205,8 @@ required_limb_biostate = BIO_JOINTED + threshold_minimum = 35 + /datum/wound/blunt/bone/moderate/Destroy() if(victim) UnregisterSignal(victim, COMSIG_LIVING_DOORCRUSHED) @@ -350,7 +323,6 @@ interaction_efficiency_penalty = 2 limp_slowdown = 6 limp_chance = 60 - threshold_minimum = 60 threshold_penalty = 30 treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel) status_effect_type = /datum/status_effect/wound/blunt/bone/severe @@ -358,7 +330,7 @@ brain_trauma_group = BRAIN_TRAUMA_MILD trauma_cycle_cooldown = 1.5 MINUTES internal_bleeding_chance = 40 - wound_flags = (ACCEPTS_GAUZE | MANGLES_BONE) + wound_flags = (ACCEPTS_GAUZE | MANGLES_INTERIOR) regen_ticks_needed = 120 // ticks every 2 seconds, 240 seconds, so roughly 4 minutes default /datum/wound_pregen_data/bone/hairline @@ -366,6 +338,8 @@ wound_path_to_generate = /datum/wound/blunt/bone/severe + threshold_minimum = 60 + /// Compound Fracture (Critical Blunt) /datum/wound/blunt/bone/critical name = "Compound Fracture" @@ -379,7 +353,6 @@ limp_slowdown = 7 limp_chance = 70 sound_effect = 'sound/effects/wounds/crack2.ogg' - threshold_minimum = 115 threshold_penalty = 50 disabling = TRUE treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel) @@ -388,7 +361,7 @@ brain_trauma_group = BRAIN_TRAUMA_SEVERE trauma_cycle_cooldown = 2.5 MINUTES internal_bleeding_chance = 60 - wound_flags = (ACCEPTS_GAUZE | MANGLES_BONE) + wound_flags = (ACCEPTS_GAUZE | MANGLES_INTERIOR) regen_ticks_needed = 240 // ticks every 2 seconds, 480 seconds, so roughly 8 minutes default /datum/wound_pregen_data/bone/compound @@ -396,6 +369,8 @@ wound_path_to_generate = /datum/wound/blunt/bone/critical + threshold_minimum = 115 + // doesn't make much sense for "a" bone to stick out of your head /datum/wound/blunt/bone/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null, wound_source = "Unknown") if(L.body_zone == BODY_ZONE_HEAD) diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index 2d5f006508f..524cc4158a4 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -7,28 +7,24 @@ /datum/wound/burn name = "Burn Wound" a_or_from = "from" - wound_type = WOUND_BURN sound_effect = 'sound/effects/wounds/sizzle1.ogg' /datum/wound/burn/flesh name = "Burn (Flesh) Wound" a_or_from = "from" - wound_type = WOUND_BURN processes = TRUE - scar_file = FLESH_SCAR_FILE - - wound_series = WOUND_SERIES_FLESH_BURN_BASIC + default_scar_file = FLESH_SCAR_FILE treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon - // Flesh damage vars + // Flesh damage vars /// How much damage to our flesh we currently have. Once both this and infestation reach 0, the wound is considered healed var/flesh_damage = 5 /// Our current counter for how much flesh regeneration we have stacked from regenerative mesh/synthflesh/whatever, decrements each tick and lowers flesh_damage var/flesh_healing = 0 - // Infestation vars (only for severe and critical) + // Infestation vars (only for severe and critical) /// How quickly infection breeds on this burn if we don't have disinfectant var/infestation_rate = 0 /// Our current level of infection @@ -272,8 +268,11 @@ /datum/wound_pregen_data/flesh_burn abstract = TRUE + required_wounding_types = list(WOUND_BURN) required_limb_biostate = BIO_FLESH + wound_series = WOUND_SERIES_FLESH_BURN_BASIC + /datum/wound/burn/get_limb_examine_description() return span_warning("The flesh on this limb appears badly cooked.") @@ -286,7 +285,6 @@ occur_text = "breaks out with violent red burns" severity = WOUND_SEVERITY_MODERATE damage_mulitplier_penalty = 1.1 - threshold_minimum = 40 threshold_penalty = 30 // burns cause significant decrease in limb integrity compared to other wounds status_effect_type = /datum/status_effect/wound/burn/flesh/moderate flesh_damage = 5 @@ -297,6 +295,8 @@ wound_path_to_generate = /datum/wound/burn/flesh/moderate + threshold_minimum = 40 + /datum/wound/burn/flesh/severe name = "Third Degree Burns" desc = "Patient is suffering extreme burns with full skin penetration, creating serious risk of infection and greatly reduced limb integrity." @@ -305,7 +305,6 @@ occur_text = "chars rapidly, exposing ruined tissue and spreading angry red burns" severity = WOUND_SEVERITY_SEVERE damage_mulitplier_penalty = 1.2 - threshold_minimum = 80 threshold_penalty = 40 status_effect_type = /datum/status_effect/wound/burn/flesh/severe treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) @@ -318,6 +317,8 @@ wound_path_to_generate = /datum/wound/burn/flesh/severe + threshold_minimum = 80 + /datum/wound/burn/flesh/critical name = "Catastrophic Burns" desc = "Patient is suffering near complete loss of tissue and significantly charred muscle and bone, creating life-threatening risk of infection and negligible limb integrity." @@ -327,7 +328,6 @@ severity = WOUND_SEVERITY_CRITICAL damage_mulitplier_penalty = 1.3 sound_effect = 'sound/effects/wounds/sizzle2.ogg' - threshold_minimum = 140 threshold_penalty = 80 status_effect_type = /datum/status_effect/wound/burn/flesh/critical treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) @@ -340,6 +340,8 @@ wound_path_to_generate = /datum/wound/burn/flesh/critical + threshold_minimum = 140 + ///special severe wound caused by sparring interference or other god related punishments. /datum/wound/burn/flesh/severe/brand name = "Holy Brand" @@ -347,7 +349,7 @@ examine_desc = "appears to have holy symbols painfully branded into their flesh, leaving severe burns." occur_text = "chars rapidly into a strange pattern of holy symbols, burned into the flesh." -/datum/wound_pregen_data/flesh_burn/holy +/datum/wound_pregen_data/flesh_burn/third_degree/holy abstract = FALSE can_be_randomly_generated = FALSE @@ -363,7 +365,7 @@ /datum/wound/burn/flesh/severe/cursed_brand/get_limb_examine_description() return span_warning("The flesh on this limb has several ornate symbols burned into it, with pitting throughout.") -/datum/wound_pregen_data/flesh_burn/cursed_brand +/datum/wound_pregen_data/flesh_burn/third_degree/cursed_brand abstract = FALSE can_be_randomly_generated = FALSE diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm index d396b3a469f..4fb5cad387f 100644 --- a/code/datums/wounds/loss.dm +++ b/code/datums/wounds/loss.dm @@ -3,7 +3,13 @@ wound_path_to_generate = /datum/wound/loss required_limb_biostate = NONE - check_for_any = TRUE + require_any_biostate = TRUE + + required_wounding_types = list(WOUND_ALL) + + wound_series = WOUND_SERIES_LOSS_BASIC + + threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it /datum/wound/loss name = "Dismemberment Wound" @@ -11,14 +17,13 @@ sound_effect = 'sound/effects/dismember.ogg' severity = WOUND_SEVERITY_LOSS - threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it status_effect_type = null scar_keyword = "dismember" wound_flags = null already_scarred = TRUE // We manually assign scars for dismembers through endround missing limbs and aheals - /// The wound_type of the attack that caused us. Used to generate the description of our scar. Currently unused, but primarily exists in case non-biological wounds are added. - var/loss_wound_type + /// The wounding_type of the attack that caused us. Used to generate the description of our scar. Currently unused, but primarily exists in case non-biological wounds are added. + var/loss_wounding_type /// 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, attack_direction) @@ -39,14 +44,14 @@ victim.visible_message(msg, span_userdanger("Your [dismembered_part.plaintext_zone] [self_msg ? self_msg : occur_text]")) - loss_wound_type = wounding_type + loss_wounding_type = wounding_type set_limb(dismembered_part) second_wind() log_wound(victim, src) if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN && victim.blood_volume) victim.spray_blood(attack_direction, severity) - dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE, wound_type = wounding_type) + dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE, wounding_type = wounding_type) qdel(src) return TRUE @@ -64,17 +69,8 @@ if(WOUND_BURN) occur_text = "is outright incinerated, falling to dust!" else - var/bone_text - if (biological_state & BIO_BONE) - bone_text = "bone" - else if (biological_state & BIO_METAL) - bone_text = "metal" - - var/tissue_text - if (biological_state & BIO_FLESH) - tissue_text = "flesh" - else if (biological_state & BIO_WIRED) - tissue_text = "wire" + var/bone_text = get_internal_description() + var/tissue_text = get_external_description() switch(wounding_type) if(WOUND_BLUNT) @@ -87,11 +83,3 @@ occur_text = "is completely incinerated, falling to dust!" return occur_text - -/datum/wound/loss/get_scar_file(obj/item/bodypart/scarred_limb, add_to_scars) - if (scarred_limb.biological_state & BIO_FLESH) - return FLESH_SCAR_FILE - if (scarred_limb.biological_state & BIO_BONE) - return BONE_SCAR_FILE - - return ..() diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 0d0164c5e25..6c4e75d15a7 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -3,20 +3,17 @@ Piercing wounds */ /datum/wound/pierce - wound_type = WOUND_PIERCE /datum/wound/pierce/bleed name = "Piercing Wound" sound_effect = 'sound/weapons/slice.ogg' processes = TRUE treatable_by = list(/obj/item/stack/medical/suture) - treatable_tool = TOOL_CAUTERY + treatable_tools = list(TOOL_CAUTERY) base_treat_time = 3 SECONDS - wound_flags = (ACCEPTS_GAUZE) + wound_flags = (ACCEPTS_GAUZE | CAN_BE_GRASPED) - wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED - - scar_file = FLESH_SCAR_FILE + default_scar_file = FLESH_SCAR_FILE /// How much blood we start losing when this wound is first applied var/initial_flow @@ -30,13 +27,13 @@ /datum/wound/pierce/bleed/wound_injury(datum/wound/old_wound = null, attack_direction = null) set_blood_flow(initial_flow) - if(!no_bleeding && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) + if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) victim.spray_blood(attack_direction, severity) return ..() /datum/wound/pierce/bleed/receive_damage(wounding_type, wounding_dmg, wound_bonus) - if(victim.stat == DEAD || (wounding_dmg < 5) || no_bleeding || !victim.blood_volume || !prob(internal_bleeding_chance + wounding_dmg)) + if(victim.stat == DEAD || (wounding_dmg < 5) || !limb.can_bleed() || !victim.blood_volume || !prob(internal_bleeding_chance + wounding_dmg)) return if(limb.current_gauze?.splint_factor) wounding_dmg *= (1 - limb.current_gauze.splint_factor) @@ -59,7 +56,7 @@ /datum/wound/pierce/bleed/get_bleed_rate_of_change() //basically if a species doesn't bleed, the wound is stagnant and will not heal on it's own (nor get worse) - if(no_bleeding) + if(!limb.can_bleed()) return BLOOD_FLOW_STEADY if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) return BLOOD_FLOW_INCREASING @@ -73,7 +70,7 @@ set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)) - if(!no_bleeding) + if(limb.can_bleed()) if(victim.bodytemperature < (BODYTEMP_NORMAL - 10)) adjust_blood_flow(-0.1 * seconds_per_tick) if(SPT_PROB(2.5, seconds_per_tick)) @@ -127,7 +124,7 @@ if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) return TRUE - var/bleeding_wording = (no_bleeding ? "holes" : "bleeding") + var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding") user.visible_message(span_green("[user] stitches up some of the [bleeding_wording] on [victim]."), span_green("You stitch up some of the [bleeding_wording] on [user == victim ? "yourself" : "[victim]"].")) var/blood_sutured = I.stop_bleeding / self_penalty_mult adjust_blood_flow(-blood_sutured) @@ -157,7 +154,7 @@ if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) return TRUE - var/bleeding_wording = (no_bleeding ? "holes" : "bleeding") + var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding") user.visible_message(span_green("[user] cauterizes some of the [bleeding_wording] on [victim]."), span_green("You cauterize some of the [bleeding_wording] on [victim].")) limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND) if(prob(30)) @@ -173,6 +170,9 @@ abstract = TRUE required_limb_biostate = (BIO_FLESH) + required_wounding_types = list(WOUND_PIERCE) + + wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED /datum/wound/pierce/get_limb_examine_description() return span_warning("The flesh on this limb appears badly perforated.") @@ -189,7 +189,6 @@ gauzed_clot_rate = 0.8 internal_bleeding_chance = 30 internal_bleeding_coefficient = 1.25 - threshold_minimum = 30 threshold_penalty = 20 status_effect_type = /datum/status_effect/wound/pierce/moderate scar_keyword = "piercemoderate" @@ -199,8 +198,10 @@ wound_path_to_generate = /datum/wound/pierce/bleed/moderate + threshold_minimum = 30 + /datum/wound/pierce/bleed/moderate/update_descriptions() - if(no_bleeding) + if(!limb.can_bleed()) examine_desc = "has a small, circular hole" occur_text = "splits a small hole open" @@ -216,7 +217,6 @@ gauzed_clot_rate = 0.6 internal_bleeding_chance = 60 internal_bleeding_coefficient = 1.5 - threshold_minimum = 50 threshold_penalty = 35 status_effect_type = /datum/status_effect/wound/pierce/severe scar_keyword = "piercesevere" @@ -226,8 +226,10 @@ wound_path_to_generate = /datum/wound/pierce/bleed/severe + threshold_minimum = 50 + /datum/wound/pierce/bleed/severe/update_descriptions() - if(no_bleeding) + if(!limb.can_bleed()) occur_text = "tears a hole open" /datum/wound/pierce/bleed/critical @@ -242,13 +244,14 @@ gauzed_clot_rate = 0.4 internal_bleeding_chance = 80 internal_bleeding_coefficient = 1.75 - threshold_minimum = 100 threshold_penalty = 50 status_effect_type = /datum/status_effect/wound/pierce/critical scar_keyword = "piercecritical" - wound_flags = (ACCEPTS_GAUZE | MANGLES_FLESH) + wound_flags = (ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED) /datum/wound_pregen_data/flesh_pierce/cavity abstract = FALSE wound_path_to_generate = /datum/wound/pierce/bleed/critical + + threshold_minimum = 100 diff --git a/code/datums/wounds/scars/_scars.dm b/code/datums/wounds/scars/_scars.dm index bf33af3fefc..7989e4a71e5 100644 --- a/code/datums/wounds/scars/_scars.dm +++ b/code/datums/wounds/scars/_scars.dm @@ -61,7 +61,7 @@ return required_limb_biostate = pregen_data.required_limb_biostate - check_any_biostates = pregen_data.check_for_any + check_any_biostates = pregen_data.require_any_biostate limb = BP RegisterSignal(limb, COMSIG_QDELETING, PROC_REF(limb_gone)) @@ -103,7 +103,7 @@ LAZYADD(victim.all_scars, src) /// Used to "load" a persistent scar -/datum/scar/proc/load(obj/item/bodypart/BP, version, description, specific_location, severity = WOUND_SEVERITY_SEVERE, required_limb_biostate = BIO_STANDARD, char_slot, check_any_biostates = FALSE) +/datum/scar/proc/load(obj/item/bodypart/BP, version, description, specific_location, severity = WOUND_SEVERITY_SEVERE, required_limb_biostate = BIO_STANDARD_UNJOINTED, char_slot, check_any_biostates = FALSE) if(!BP.scarrable) qdel(src) return diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index 0d552d6910c..6f7ec3017b8 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -6,26 +6,25 @@ /datum/wound/slash name = "Slashing (Cut) Wound" sound_effect = 'sound/weapons/slice.ogg' - wound_type = WOUND_SLASH /datum/wound_pregen_data/flesh_slash abstract = TRUE + required_wounding_types = list(WOUND_SLASH) required_limb_biostate = BIO_FLESH + wound_series = WOUND_SERIES_FLESH_SLASH_BLEED + /datum/wound/slash/flesh name = "Slashing (Cut) Flesh Wound" processes = TRUE - wound_type = WOUND_SLASH treatable_by = list(/obj/item/stack/medical/suture) treatable_by_grabbed = list(/obj/item/gun/energy/laser) - treatable_tool = TOOL_CAUTERY + treatable_tools = list(TOOL_CAUTERY) base_treat_time = 3 SECONDS - wound_flags = (ACCEPTS_GAUZE) + wound_flags = (ACCEPTS_GAUZE|CAN_BE_GRASPED) - scar_file = FLESH_SCAR_FILE - - wound_series = WOUND_SERIES_FLESH_SLASH_BLEED + default_scar_file = FLESH_SCAR_FILE /// How much blood we start losing when this wound is first applied var/initial_flow @@ -43,6 +42,11 @@ /// 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/flesh/Destroy() + highest_scar = null + + return ..() + /datum/wound/slash/flesh/wound_injury(datum/wound/slash/flesh/old_wound = null, attack_direction = null) if(old_wound) set_blood_flow(max(old_wound.blood_flow, initial_flow)) @@ -51,7 +55,7 @@ old_wound.clear_highest_scar() else set_blood_flow(initial_flow) - if(!no_bleeding && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) + if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) victim.spray_blood(attack_direction, severity) if(!highest_scar) @@ -119,7 +123,7 @@ /datum/wound/slash/flesh/get_bleed_rate_of_change() //basically if a species doesn't bleed, the wound is stagnant and will not heal on it's own (nor get worse) - if(no_bleeding) + if(!limb.can_bleed()) return BLOOD_FLOW_STEADY if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) return BLOOD_FLOW_INCREASING @@ -134,7 +138,7 @@ return // in case the victim has the NOBLOOD trait, the wound will simply not clot on it's own - if(!no_bleeding) + if(limb.can_bleed()) set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)) if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) @@ -147,7 +151,7 @@ adjust_blood_flow(-limb.current_gauze.absorption_rate * seconds_per_tick) limb.seep_gauze(limb.current_gauze.absorption_rate * seconds_per_tick) //otherwise, only clot if it's a bleeder - else if(!no_bleeding) + else if(limb.can_bleed()) adjust_blood_flow(-clot_rate * seconds_per_tick) if(blood_flow > highest_flow) @@ -157,7 +161,7 @@ if(demotes_to) replace_wound(new demotes_to) else - to_chat(victim, span_green("The cut on your [limb.plaintext_zone] has [no_bleeding ? "healed up" : "stopped bleeding"]!")) + to_chat(victim, span_green("The cut on your [limb.plaintext_zone] has [!limb.can_bleed() ? "healed up" : "stopped bleeding"]!")) qdel(src) /datum/wound/slash/flesh/on_stasis(seconds_per_tick, times_fired) @@ -225,7 +229,7 @@ /datum/wound/slash/flesh/on_xadone(power) . = ..() - + if (limb) // parent can cause us to be removed, so its reasonable to check if we're still applied adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort @@ -264,7 +268,7 @@ if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) return - var/bleeding_wording = (no_bleeding ? "cuts" : "bleeding") + var/bleeding_wording = (!limb.can_bleed() ? "cuts" : "bleeding") user.visible_message(span_green("[user] cauterizes some of the [bleeding_wording] on [victim]."), span_green("You cauterize some of the [bleeding_wording] on [victim].")) limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND) if(prob(30)) @@ -292,7 +296,7 @@ if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) return TRUE - var/bleeding_wording = (no_bleeding ? "cuts" : "bleeding") + var/bleeding_wording = (!limb.can_bleed() ? "cuts" : "bleeding") user.visible_message(span_green("[user] stitches up some of the [bleeding_wording] on [victim]."), span_green("You stitch up some of the [bleeding_wording] on [user == victim ? "yourself" : "[victim]"].")) var/blood_sutured = I.stop_bleeding / self_penalty_mult adjust_blood_flow(-blood_sutured) @@ -320,13 +324,12 @@ initial_flow = 2 minimum_flow = 0.5 clot_rate = 0.05 - threshold_minimum = 20 threshold_penalty = 10 status_effect_type = /datum/status_effect/wound/slash/flesh/moderate scar_keyword = "slashmoderate" /datum/wound/slash/flesh/moderate/update_descriptions() - if(no_bleeding) + if(!limb.can_bleed()) occur_text = "is cut open" /datum/wound_pregen_data/flesh_slash/abrasion @@ -334,6 +337,8 @@ wound_path_to_generate = /datum/wound/slash/flesh/moderate + threshold_minimum = 20 + /datum/wound/slash/flesh/severe name = "Open Laceration" desc = "Patient's skin is ripped clean open, allowing significant blood loss." @@ -345,7 +350,6 @@ initial_flow = 3.25 minimum_flow = 2.75 clot_rate = 0.03 - threshold_minimum = 50 threshold_penalty = 25 demotes_to = /datum/wound/slash/flesh/moderate status_effect_type = /datum/status_effect/wound/slash/flesh/severe @@ -356,8 +360,10 @@ wound_path_to_generate = /datum/wound/slash/flesh/severe + threshold_minimum = 50 + /datum/wound/slash/flesh/severe/update_descriptions() - if(no_bleeding) + if(!limb.can_bleed()) occur_text = "is ripped open" /datum/wound/slash/flesh/critical @@ -371,25 +377,30 @@ initial_flow = 4 minimum_flow = 3.85 clot_rate = -0.015 // critical cuts actively get worse instead of better - threshold_minimum = 80 threshold_penalty = 40 demotes_to = /datum/wound/slash/flesh/severe status_effect_type = /datum/status_effect/wound/slash/flesh/critical scar_keyword = "slashcritical" - wound_flags = (ACCEPTS_GAUZE | MANGLES_FLESH) + wound_flags = (ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED) + +/datum/wound/slash/flesh/critical/update_descriptions() + if (!limb.can_bleed()) + occur_text = "is torn open" /datum/wound_pregen_data/flesh_slash/avulsion abstract = FALSE wound_path_to_generate = /datum/wound/slash/flesh/critical + threshold_minimum = 80 + /datum/wound/slash/flesh/moderate/many_cuts name = "Numerous Small Slashes" desc = "Patient's skin has numerous small slashes and cuts, generating moderate blood loss." examine_desc = "has a ton of small cuts" occur_text = "is cut numerous times, leaving many small slashes." -/datum/wound_pregen_data/flesh_slash/cuts +/datum/wound_pregen_data/flesh_slash/abrasion/cuts abstract = FALSE can_be_randomly_generated = FALSE @@ -402,10 +413,10 @@ clot_rate = 0.01 /datum/wound/slash/flesh/critical/cleave/update_descriptions() - if(no_bleeding) + if(!limb.can_bleed()) occur_text = "is ruptured" -/datum/wound_pregen_data/flesh_slash/cleave +/datum/wound_pregen_data/flesh_slash/avulsion/clear abstract = FALSE can_be_randomly_generated = FALSE diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm index 816814b1bcf..7c73d2b6f65 100644 --- a/code/game/machinery/iv_drip.dm +++ b/code/game/machinery/iv_drip.dm @@ -243,12 +243,12 @@ if(!(get_dist(src, attached) <= 1 && isturf(attached.loc))) if(isliving(attached)) - var/mob/living/attached_mob = attached + var/mob/living/carbon/attached_mob = attached to_chat(attached, span_userdanger("The IV drip needle is ripped out of you, leaving an open bleeding wound!")) var/list/arm_zones = shuffle(list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM)) var/obj/item/bodypart/chosen_limb = attached_mob.get_bodypart(arm_zones[1]) || attached_mob.get_bodypart(arm_zones[2]) || attached_mob.get_bodypart(BODY_ZONE_CHEST) chosen_limb.receive_damage(3) - chosen_limb.force_wound_upwards(/datum/wound/pierce/bleed/moderate, wound_source = "IV needle") + attached_mob.cause_wound_of_type_and_severity(WOUND_PIERCE, chosen_limb, WOUND_SEVERITY_MODERATE, wound_source = "IV needle") else visible_message(span_warning("[attached] is detached from [src].")) detach_iv() diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index b136fc68cda..5dfb27e77ea 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -128,7 +128,7 @@ return FALSE var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD) - if(!(the_head.biological_state & BIO_FLESH) || !IS_ORGANIC_LIMB(the_head)) + if(!(the_head.biological_state & BIO_FLESH)) to_chat(user, span_warning("You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!")) return diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index d071e3465af..081ab5d78e0 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -433,7 +433,7 @@ patient.emote("scream") for(var/i in patient.bodyparts) - var/obj/item/bodypart/bone = i + var/obj/item/bodypart/bone = i // fine to just, use these raw, its a meme anyway var/datum/wound/blunt/bone/severe/oof_ouch = new oof_ouch.apply_wound(bone, wound_source = "bone gel") var/datum/wound/blunt/bone/critical/oof_OUCH = new diff --git a/code/modules/actionspeed/_actionspeed_modifier.dm b/code/modules/actionspeed/_actionspeed_modifier.dm index 71bc966acf4..761bfc3ff74 100644 --- a/code/modules/actionspeed/_actionspeed_modifier.dm +++ b/code/modules/actionspeed/_actionspeed_modifier.dm @@ -37,8 +37,11 @@ can next move /// Other modification datums this conflicts with. var/conflicts_with -/datum/actionspeed_modifier/New() +/datum/actionspeed_modifier/New(init_id) . = ..() + + id = init_id + if(!id) id = "[type]" //We turn the path into a string. diff --git a/code/modules/actionspeed/modifiers/wound.dm b/code/modules/actionspeed/modifiers/wound.dm new file mode 100644 index 00000000000..845399e0761 --- /dev/null +++ b/code/modules/actionspeed/modifiers/wound.dm @@ -0,0 +1,10 @@ +/datum/actionspeed_modifier/wound_interaction_inefficiency + variable = TRUE + + var/datum/wound/parent + +/datum/actionspeed_modifier/wound_interaction_inefficiency/New(new_id, datum/wound/parent) + + src.parent = parent + + return ..() diff --git a/code/modules/admin/smites/bloodless.dm b/code/modules/admin/smites/bloodless.dm index db68a1cd3a2..c970e920f22 100644 --- a/code/modules/admin/smites/bloodless.dm +++ b/code/modules/admin/smites/bloodless.dm @@ -9,7 +9,7 @@ return var/mob/living/carbon/carbon_target = target for(var/_limb in carbon_target.bodyparts) - var/obj/item/bodypart/limb = _limb + var/obj/item/bodypart/limb = _limb // fine to use this raw, its a meme smite var/type_wound = pick(list(/datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate)) limb.force_wound_upwards(type_wound, smited = TRUE) type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate)) diff --git a/code/modules/admin/smites/boneless.dm b/code/modules/admin/smites/boneless.dm index 5d859669a68..bf402abdfdb 100644 --- a/code/modules/admin/smites/boneless.dm +++ b/code/modules/admin/smites/boneless.dm @@ -11,11 +11,11 @@ var/mob/living/carbon/carbon_target = target for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts) - var/type_wound = pick(list( - /datum/wound/blunt/bone/critical, - /datum/wound/blunt/bone/severe, - /datum/wound/blunt/bone/critical, - /datum/wound/blunt/bone/severe, - /datum/wound/blunt/bone/moderate, + var/severity = pick(list( + "[WOUND_SEVERITY_MODERATE]", + "[WOUND_SEVERITY_SEVERE]", + "[WOUND_SEVERITY_SEVERE]", + "[WOUND_SEVERITY_CRITICAL]", + "[WOUND_SEVERITY_CRITICAL]", )) - limb.force_wound_upwards(type_wound, smited = TRUE) + carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, severity) diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index 3b16fffd9d7..c688e285735 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -269,8 +269,7 @@ var/mob/living/carbon/carbon_target = target var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts) - var/datum/wound/slash/flesh/severe/crit_wound = new() - crit_wound.apply_wound(bodypart, attack_direction = get_dir(source, target)) + carbon_target.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL) /datum/heretic_knowledge/summon/stalker name = "Lonely Ritual" diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm index 743aa2c5a01..0f6809a898a 100644 --- a/code/modules/antagonists/heretic/status_effects/buffs.dm +++ b/code/modules/antagonists/heretic/status_effects/buffs.dm @@ -74,7 +74,8 @@ heal_amt = 3 if(WOUND_SEVERITY_CRITICAL) heal_amt = 6 - if(wound.wound_type == WOUND_BURN) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound.type] + if (pregen_data.wounding_types_valid(list(WOUND_BURN))) carbie.adjustFireLoss(-heal_amt) else carbie.adjustBruteLoss(-heal_amt) diff --git a/code/modules/antagonists/heretic/status_effects/mark_effects.dm b/code/modules/antagonists/heretic/status_effects/mark_effects.dm index c454ebdc462..d5f759ec962 100644 --- a/code/modules/antagonists/heretic/status_effects/mark_effects.dm +++ b/code/modules/antagonists/heretic/status_effects/mark_effects.dm @@ -61,8 +61,7 @@ if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner var/obj/item/bodypart/bodypart = pick(human_owner.bodyparts) - var/datum/wound/slash/flesh/severe/crit_wound = new() - crit_wound.apply_wound(bodypart) + human_owner.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_SEVERE) return ..() diff --git a/code/modules/antagonists/traitor/objectives/eyesnatching.dm b/code/modules/antagonists/traitor/objectives/eyesnatching.dm index d912be2384a..0540d83601c 100644 --- a/code/modules/antagonists/traitor/objectives/eyesnatching.dm +++ b/code/modules/antagonists/traitor/objectives/eyesnatching.dm @@ -179,9 +179,10 @@ if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) return - var/datum/wound/blunt/bone/severe/severe_wound_type = /datum/wound/blunt/bone/severe - var/datum/wound/blunt/bone/critical/critical_wound_type = /datum/wound/blunt/bone/critical - target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10), attacking_item = src) + var/min_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src) + var/max_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src) + + target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src) target.visible_message( span_danger("[src] pierces through [target]'s skull, horribly mutilating their eyes!"), span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"), diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm index ca2b6ec39c9..fc79c82e780 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -13,8 +13,12 @@ /obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human) . = ..() - var/datum/wound/pierce/bleed/critical/exit_hole = new() - exit_hole.apply_wound(spawned_human.get_bodypart(BODY_ZONE_CHEST)) + + var/obj/item/bodypart/chest/their_chest = spawned_human.get_bodypart(BODY_ZONE_CHEST) + if (!their_chest) + return + + spawned_human.cause_wound_of_type_and_severity(WOUND_PIERCE, their_chest, WOUND_SEVERITY_CRITICAL) /// A fun drink enjoyed by the tiger cooperative, might corrode your brain if you drink the whole bottle /obj/item/reagent_containers/cup/glass/bottle/ritual_wine diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 7d65b50afbb..67774a3074a 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -100,7 +100,7 @@ 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 && IS_ORGANIC_LIMB(affecting)) + if(I.damtype == BRUTE && affecting.can_bleed()) if(prob(33)) I.add_mob_blood(src) var/turf/location = get_turf(src) @@ -127,22 +127,35 @@ var/message_verb_simple = length(I.attack_verb_simple) ? "[pick(I.attack_verb_simple)]" : "attack" var/extra_wound_details = "" + if(I.damtype == BRUTE && hit_bodypart.can_dismember()) + var/mangled_state = hit_bodypart.get_mangled_state() - var/bio_state = hit_bodypart.biological_state - if((mangled_state & BODYPART_MANGLED_FLESH) && (mangled_state & BODYPART_MANGLED_BONE)) + + var/bio_status = hit_bodypart.get_bio_state_status() + + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) + var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR))) + + var/dismemberable = ((hit_bodypart.dismemberable_by_wound()) || hit_bodypart.dismemberable_by_total_damage()) + if (dismemberable) extra_wound_details = ", threatening to sever it entirely" - else if((mangled_state & BODYPART_MANGLED_FLESH && I.get_sharpness()) || ((mangled_state & BODYPART_MANGLED_BONE) && (bio_state & BIO_BONE) && !(bio_state & BIO_FLESH))) - extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the bone" - else if((mangled_state & BODYPART_MANGLED_BONE && I.get_sharpness()) || ((mangled_state & BODYPART_MANGLED_FLESH) && (bio_state & BIO_FLESH) && !(bio_state & BIO_BONE))) - extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining tissue" + else if((has_interior && (has_exterior && exterior_ready_to_dismember) && I.get_sharpness())) + var/bone_text = hit_bodypart.get_internal_description() + extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the [bone_text]" + else if(has_exterior && ((has_interior && interior_ready_to_dismember) && I.get_sharpness())) + var/tissue_text = hit_bodypart.get_external_description() + extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining [tissue_text]" var/message_hit_area = "" if(hit_area) message_hit_area = " in the [hit_area]" var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!" var/attack_message_victim = "You're [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!" - var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I]!" + var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I][extra_wound_details]!" if(user in viewers(src, null)) attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [I][extra_wound_details]!" attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [I][extra_wound_details]!" @@ -698,14 +711,16 @@ return ..() var/obj/item/bodypart/grasped_part = get_bodypart(zone_selected) - if(!grasped_part?.get_modified_bleed_rate()) + if(!grasped_part?.can_be_grasped()) return var/starting_hand_index = active_hand_index if(starting_hand_index == grasped_part.held_index) to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!")) return - to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding...")) + var/bleed_rate = grasped_part.get_modified_bleed_rate() + var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "") + to_chat(src, span_warning("You try grasping at your [grasped_part.name][bleeding_text]...")) if(!do_after(src, 0.75 SECONDS)) to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) return @@ -717,6 +732,17 @@ return grasp.grasp_limb(grasped_part) +/// If TRUE, the owner of this bodypart can try grabbing it to slow bleeding, as well as various other effects. +/obj/item/bodypart/proc/can_be_grasped() + if (get_modified_bleed_rate()) + return TRUE + + for (var/datum/wound/iterated_wound as anything in wounds) + if (iterated_wound.wound_flags & CAN_BE_GRASPED) + return TRUE + + return FALSE + /// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this. /obj/item/hand_item/self_grasp name = "self-grasp" @@ -761,7 +787,9 @@ RegisterSignal(user, COMSIG_QDELETING, PROC_REF(qdel_void)) RegisterSignals(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_QDELETING), PROC_REF(qdel_void)) - user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) + var/bleed_rate = grasped_part.get_modified_bleed_rate() + var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "") + user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name][bleeding_text]."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) return TRUE diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 623fa4b0fec..b22b32583ab 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -104,7 +104,7 @@ /// All of the scars a carbon has afflicted throughout their limbs var/list/all_scars - /// Assoc list of BODY_ZONE -> WOUND_TYPE. Set when a limb is dismembered, unset when one is attached. Used for determining what scar to add when it comes time to generate them. + /// Assoc list of BODY_ZONE -> wounding_type. Set when a limb is dismembered, unset when one is attached. Used for determining what scar to add when it comes time to generate them. var/list/body_zone_dismembered_by /// Simple modifier for whether this mob can handle greater or lesser skillchip complexity. See /datum/mutation/human/biotechcompat/ for example. diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 34bfccde5ae..8aca8a4a947 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1667,19 +1667,26 @@ GLOBAL_LIST_EMPTY(features_by_species) // Lets pick a random body part and check for an existing burn var/obj/item/bodypart/bodypart = pick(humi.bodyparts) - var/datum/wound/burn/flesh/existing_burn = locate(/datum/wound/burn) in bodypart.wounds - + var/datum/wound/existing_burn + for (var/datum/wound/iterated_wound as anything in bodypart.wounds) + var/datum/wound_pregen_data/pregen_data = iterated_wound.get_pregen_data() + if (pregen_data.wound_series in GLOB.wounding_types_to_series[WOUND_BURN]) + existing_burn = iterated_wound + break // If we have an existing burn try to upgrade it + var/severity if(existing_burn) switch(existing_burn.severity) if(WOUND_SEVERITY_MODERATE) if(humi.bodytemperature > BODYTEMP_HEAT_WOUND_LIMIT + 400) // 800k - bodypart.force_wound_upwards(/datum/wound/burn/flesh/severe, wound_source = "hot temperatures") + severity = WOUND_SEVERITY_SEVERE if(WOUND_SEVERITY_SEVERE) if(humi.bodytemperature > BODYTEMP_HEAT_WOUND_LIMIT + 2800) // 3200k - bodypart.force_wound_upwards(/datum/wound/burn/flesh/critical, wound_source = "hot temperatures") + severity = WOUND_SEVERITY_CRITICAL else // If we have no burn apply the lowest level burn - bodypart.force_wound_upwards(/datum/wound/burn/flesh/moderate, wound_source = "hot temperatures") + severity = WOUND_SEVERITY_MODERATE + + humi.cause_wound_of_type_and_severity(WOUND_BURN, bodypart, severity, wound_source = "hot temperatures") // always take some burn damage var/burn_damage = HEAT_DAMAGE_LEVEL_1 diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm index 31cbe153f9e..9586ec6e861 100644 --- a/code/modules/paperwork/paper_cutter.dm +++ b/code/modules/paperwork/paper_cutter.dm @@ -171,8 +171,9 @@ to_chat(user, span_userdanger("You neatly cut [stored_paper][clumsy ? "... and your finger in the process!" : "."]")) if(clumsy) var/obj/item/bodypart/finger = user.get_active_hand() - var/datum/wound/slash/flesh/moderate/papercut = new - papercut.apply_wound(finger, wound_source = "paper cut") + if (iscarbon(user)) + var/mob/living/carbon/carbon_user = user + carbon_user.cause_wound_of_type_and_severity(WOUND_SLASH, finger, WOUND_SEVERITY_MODERATE, wound_source = "paper cut") stored_paper = null qdel(stored_paper) new /obj/item/paper/paperslip(get_turf(src)) diff --git a/code/modules/spells/spell_types/touch/scream_for_me.dm b/code/modules/spells/spell_types/touch/scream_for_me.dm index e10bdaebcc5..231b6927e50 100644 --- a/code/modules/spells/spell_types/touch/scream_for_me.dm +++ b/code/modules/spells/spell_types/touch/scream_for_me.dm @@ -21,7 +21,7 @@ span_userdanger("The spell bounces from [victim]'s skin back into your arm!"), ) var/obj/item/bodypart/to_wound = caster.get_holding_bodypart_of_item(hand) - to_wound.force_wound_upwards(/datum/wound/slash/flesh/critical) + caster.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL) /datum/action/cooldown/spell/touch/scream_for_me/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster) if(!ishuman(victim)) @@ -29,7 +29,7 @@ var/mob/living/carbon/human/human_victim = victim human_victim.emote("scream") for(var/obj/item/bodypart/to_wound as anything in human_victim.bodyparts) - to_wound.force_wound_upwards(/datum/wound/slash/flesh/critical) + human_victim.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL) return TRUE /obj/item/melee/touch_attack/scream_for_me diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index a035966bb52..1262f24f9cf 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -31,9 +31,9 @@ /** * A bitfield of biological states, exclusively used to determine which wounds this limb will get, * as well as how easily it will happen. - * Set to BIO_STANDARD because most species have both flesh bone and blood in their limbs. + * Set to BIO_STANDARD_UNJOINTED because most species have both flesh bone and blood in their limbs. */ - var/biological_state = BIO_STANDARD + var/biological_state = BIO_STANDARD_UNJOINTED ///A bitfield of bodytypes for clothing, surgery, and misc information var/bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC ///Defines when a bodypart should not be changed. Example: BP_BLOCK_CHANGE_SPECIES prevents the limb from being overwritten on species gain @@ -195,10 +195,10 @@ var/hp_percent_to_dismemberable = 0.8 /// If true, we will use [hp_percent_to_dismemberable] even if we are dismemberable via wounds. Useful for things with extreme wound resistance. var/use_alternate_dismemberment_calc_even_if_mangleable = FALSE - /// If false, no wound that can be applied to us can mangle our flesh. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment. - var/any_existing_wound_can_mangle_our_flesh - /// If false, no wound that can be applied to us can mangle our bone. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment. - var/any_existing_wound_can_mangle_our_bone + /// If false, no wound that can be applied to us can mangle our exterior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment. + 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 /obj/item/bodypart/apply_fantasy_bonuses(bonus) . = ..() @@ -495,73 +495,32 @@ var/mangled_state = get_mangled_state() var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%) - var/has_exterior = FALSE - var/has_interior = FALSE + var/bio_status = get_bio_state_status() - for (var/state as anything in GLOB.bio_state_states) - var/flag = text2num(state) - if (!(biological_state & flag)) - continue + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) - var/value = GLOB.bio_state_states[state] - if (value & BIO_EXTERIOR) - has_exterior = TRUE - if (value & BIO_INTERIOR) - has_interior = TRUE - - if (has_exterior && has_interior) - break - - // We put this here so we dont increase init time by doing this all at once on initialization - // Effectively, we "lazy load" - if (isnull(any_existing_wound_can_mangle_our_bone) || isnull(any_existing_wound_can_mangle_our_flesh)) - any_existing_wound_can_mangle_our_bone = FALSE - any_existing_wound_can_mangle_our_flesh = FALSE - for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data) - var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type] - if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific - continue - if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_FLESH) - any_existing_wound_can_mangle_our_flesh = TRUE - if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_BONE) - any_existing_wound_can_mangle_our_bone = TRUE - - if (any_existing_wound_can_mangle_our_bone && any_existing_wound_can_mangle_our_flesh) - break - - var/can_theoretically_be_dismembered = (any_existing_wound_can_mangle_our_bone || (any_existing_wound_can_mangle_our_flesh && !has_exterior)) - - var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_BONE) == BODYPART_MANGLED_BONE)) - var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_FLESH) == BODYPART_MANGLED_FLESH)) + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) // if we're bone only, all cutting attacks go straight to the bone - if(has_exterior && interior_ready_to_dismember) + if(!has_exterior && has_interior) if(wounding_type == WOUND_SLASH) wounding_type = WOUND_BLUNT wounding_dmg *= (easy_dismember ? 1 : 0.6) else if(wounding_type == WOUND_PIERCE) wounding_type = WOUND_BLUNT wounding_dmg *= (easy_dismember ? 1 : 0.75) - if(exterior_ready_to_dismember && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) - return else // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate // So a big sharp weapon is still all you need to destroy a limb - if(has_exterior && interior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_BONE) && sharpness) - playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100) + if(has_interior && exterior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_INTERIOR) && sharpness) if(wounding_type == WOUND_SLASH && !easy_dismember) wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area if(wounding_type == WOUND_PIERCE && !easy_dismember) wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated wounding_type = WOUND_BLUNT - else if(interior_ready_to_dismember && exterior_ready_to_dismember && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) - return - if (use_alternate_dismemberment_calc_even_if_mangleable || !can_theoretically_be_dismembered) - var/percent_to_total_max = (get_damage() / max_damage) - if (percent_to_total_max >= hp_percent_to_dismemberable) - if (try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) - return - + if ((dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) + return // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage if(wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND) check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, attack_direction, damage_source = damage_source) @@ -594,6 +553,83 @@ owner.updatehealth() return update_bodypart_damage_state() || . +/// Returns a bitflag using ANATOMY_EXTERIOR or ANATOMY_INTERIOR. Used to determine if we as a whole have a interior or exterior biostate, or both. +/obj/item/bodypart/proc/get_bio_state_status() + SHOULD_BE_PURE(TRUE) + + var/bio_status = NONE + + for (var/state as anything in GLOB.bio_state_anatomy) + var/flag = text2num(state) + if (!(biological_state & flag)) + continue + + var/value = GLOB.bio_state_anatomy[state] + if (value & ANATOMY_EXTERIOR) + bio_status |= ANATOMY_EXTERIOR + if (value & ANATOMY_INTERIOR) + bio_status |= ANATOMY_INTERIOR + + if ((bio_status & ANATOMY_EXTERIOR_AND_INTERIOR) == ANATOMY_EXTERIOR_AND_INTERIOR) + break + + return bio_status + +/// Returns if our current mangling status allows us to be dismembered. Requires both no exterior/mangled exterior and no interior/mangled interior. +/obj/item/bodypart/proc/dismemberable_by_wound() + SHOULD_BE_PURE(TRUE) + + var/mangled_state = get_mangled_state() + + var/bio_status = get_bio_state_status() + + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) + var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR))) + + return (exterior_ready_to_dismember && interior_ready_to_dismember) + +/// Returns TRUE if our total percent damage is more or equal to our dismemberable percentage, but FALSE if a wound can cause us to be dismembered. +/obj/item/bodypart/proc/dismemberable_by_total_damage() + + update_wound_theory() + + var/bio_status = get_bio_state_status() + + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) + var/can_theoretically_be_dismembered_by_wound = (any_existing_wound_can_mangle_our_interior || (any_existing_wound_can_mangle_our_exterior && has_interior)) + + var/wound_dismemberable = dismemberable_by_wound() + var/ready_to_use_alternate_formula = (use_alternate_dismemberment_calc_even_if_mangleable || (!wound_dismemberable && !can_theoretically_be_dismembered_by_wound)) + + if (ready_to_use_alternate_formula) + var/percent_to_total_max = (get_damage() / max_damage) + if (percent_to_total_max >= hp_percent_to_dismemberable) + return TRUE + + return FALSE + +/// Updates our "can be theoretically dismembered by wounds" variables by iterating through all wound static data. +/obj/item/bodypart/proc/update_wound_theory() + // We put this here so we dont increase init time by doing this all at once on initialization + // Effectively, we "lazy load" + if (isnull(any_existing_wound_can_mangle_our_interior) || isnull(any_existing_wound_can_mangle_our_exterior)) + any_existing_wound_can_mangle_our_interior = FALSE + any_existing_wound_can_mangle_our_exterior = FALSE + for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type] + if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific + continue + if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_EXTERIOR) + any_existing_wound_can_mangle_our_exterior = TRUE + if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_INTERIOR) + any_existing_wound_can_mangle_our_interior = TRUE + + if (any_existing_wound_can_mangle_our_interior && any_existing_wound_can_mangle_our_exterior) + break + //Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all. //Damage cannot go below zero. //Cannot remove negative damage (i.e. apply damage) @@ -1105,9 +1141,6 @@ for(var/datum/wound/iter_wound as anything in wounds) cached_bleed_rate += iter_wound.blood_flow - if(!cached_bleed_rate) - QDEL_NULL(grasped_by) - // Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you? if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() @@ -1280,3 +1313,21 @@ /obj/item/bodypart/proc/un_paralyze() REMOVE_TRAITS_IN(src, EMP_TRAIT) + +/// Returns the generic description of our BIO_EXTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. +/obj/item/bodypart/proc/get_external_description() + if (biological_state & BIO_FLESH) + return "flesh" + if (biological_state & BIO_WIRED) + return "wiring" + + return "error" + +/// Returns the generic description of our BIO_INTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. +/obj/item/bodypart/proc/get_internal_description() + if (biological_state & BIO_BONE) + return "bone" + if (biological_state & BIO_METAL) + return "metal" + + return "error" diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 22a325f1974..5a7343f8a0b 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -5,7 +5,7 @@ return TRUE ///Remove target limb from it's owner, with side effects. -/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE, wound_type) +/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE, wounding_type) if(!owner || (bodypart_flags & BODYPART_UNREMOVABLE)) return FALSE var/mob/living/carbon/limb_owner = owner @@ -23,14 +23,14 @@ limb_owner.add_mood_event("dismembered_[body_zone]", /datum/mood_event/dismembered, src) limb_owner.add_mob_memory(/datum/memory/was_dismembered, lost_limb = src) - if (wound_type) - LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wound_type) + if (wounding_type) + LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wounding_type) 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(wound_type != WOUND_BURN && istype(owner_location) && can_bleed()) + if(wounding_type != WOUND_BURN && istype(owner_location) && can_bleed()) limb_owner.add_splatter_floor(owner_location) if(QDELETED(src)) //Could have dropped into lava/explosion/chasm/whatever @@ -55,7 +55,7 @@ return TRUE -/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent=TRUE, wound_type) +/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent=TRUE, wounding_type) if(!owner) return FALSE var/mob/living/carbon/chest_owner = owner @@ -64,7 +64,7 @@ if(HAS_TRAIT(chest_owner, TRAIT_NODISMEMBER)) return FALSE . = list() - if(wound_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed()) + if(wounding_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed()) chest_owner.add_splatter_floor(chest_owner.loc) playsound(get_turf(chest_owner), 'sound/misc/splort.ogg', 80, TRUE) for(var/obj/item/organ/organ as anything in chest_owner.organs) @@ -158,16 +158,16 @@ * Dismemberment for flesh and bone requires the victim to have the skin on their bodypart destroyed (either a critical cut or piercing wound), and at least a hairline fracture * (severe bone), at which point we can start rolling for dismembering. The attack must also deal at least 10 damage, and must be a brute attack of some kind (sorry for now, cakehat, maybe later) * - * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_FLESH if our skin is broken, BODYPART_MANGLED_BONE if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering + * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_EXTERIOR if our skin is broken, BODYPART_MANGLED_INTERIOR if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering */ /obj/item/bodypart/proc/get_mangled_state() . = BODYPART_MANGLED_NONE for(var/datum/wound/iter_wound as anything in wounds) - if((iter_wound.wound_flags & MANGLES_BONE)) - . |= BODYPART_MANGLED_BONE - if((iter_wound.wound_flags & MANGLES_FLESH)) - . |= BODYPART_MANGLED_FLESH + if((iter_wound.wound_flags & MANGLES_INTERIOR)) + . |= BODYPART_MANGLED_INTERIOR + if((iter_wound.wound_flags & MANGLES_EXTERIOR)) + . |= BODYPART_MANGLED_EXTERIOR /** * try_dismember() is used, once we've confirmed that a flesh and bone bodypart has both the skin and bone mangled, to actually roll for it @@ -445,8 +445,8 @@ if (LAZYLEN(dismembered_by_copy)) var/datum/scar/scaries = new var/datum/wound/loss/phantom_loss = new // stolen valor, really - phantom_loss.loss_wound_type = dismembered_by_copy?[limb_zone] - if (phantom_loss.loss_wound_type) + phantom_loss.loss_wounding_type = dismembered_by_copy?[limb_zone] + if (phantom_loss.loss_wounding_type) scaries.generate(limb, phantom_loss) LAZYREMOVE(dismembered_by_copy, limb_zone) // in case we're using a passed list else diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index 0ed937f3d49..c3924163784 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -53,7 +53,7 @@ if(cavity_item) cavity_item.forceMove(drop_location()) cavity_item = null - ..() + return ..() /obj/item/bodypart/chest/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' @@ -114,7 +114,7 @@ /// Datum describing how to offset things held in the hands of this arm, the x offset IS functional here var/datum/worn_feature_offset/held_hand_offset - biological_state = (BIO_STANDARD|BIO_JOINTED) + biological_state = BIO_STANDARD_JOINTED /obj/item/bodypart/arm/Destroy() QDEL_NULL(worn_glove_offset) @@ -346,7 +346,7 @@ /// Datum describing how to offset things worn on the foot of this leg, note that an x offset won't do anything here var/datum/worn_feature_offset/worn_foot_offset - biological_state = (BIO_STANDARD|BIO_JOINTED) + biological_state = BIO_STANDARD_JOINTED /obj/item/bodypart/leg/Destroy() QDEL_NULL(worn_foot_offset) diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm index db1407953b6..3ac2071b4ac 100644 --- a/code/modules/surgery/bodyparts/wounds.dm +++ b/code/modules/surgery/bodyparts/wounds.dm @@ -1,81 +1,40 @@ /// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds -/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE) +/obj/item/bodypart/proc/painless_wound_roll(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE) SHOULD_CALL_PARENT(TRUE) - if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND || (owner.status_flags & GODMODE)) + if(!owner || wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND || (owner.status_flags & GODMODE)) return var/mangled_state = get_mangled_state() var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%) - var/has_exterior = FALSE - var/has_interior = FALSE + var/bio_status = get_bio_state_status() - for (var/state as anything in GLOB.bio_state_states) - var/flag = text2num(state) - if (!(biological_state & flag)) - continue + var/has_exterior = ((bio_status & ANATOMY_EXTERIOR)) + var/has_interior = ((bio_status & ANATOMY_INTERIOR)) - var/value = GLOB.bio_state_states[state] - if (value & BIO_EXTERIOR) - has_exterior = TRUE - if (value & BIO_INTERIOR) - has_interior = TRUE - - if (has_exterior && has_interior) - break - - // We put this here so we dont increase init time by doing this all at once on initialization - // Effectively, we "lazy load" - if (isnull(any_existing_wound_can_mangle_our_bone) || isnull(any_existing_wound_can_mangle_our_flesh)) - any_existing_wound_can_mangle_our_bone = FALSE - any_existing_wound_can_mangle_our_flesh = FALSE - for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data) - var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type] - if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific - continue - if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_FLESH) - any_existing_wound_can_mangle_our_flesh = TRUE - if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_BONE) - any_existing_wound_can_mangle_our_bone = TRUE - - if (any_existing_wound_can_mangle_our_bone && any_existing_wound_can_mangle_our_flesh) - break - - var/can_theoretically_be_dismembered = (any_existing_wound_can_mangle_our_bone || (any_existing_wound_can_mangle_our_flesh && !has_exterior)) - - var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_BONE) == BODYPART_MANGLED_BONE)) - var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_FLESH) == BODYPART_MANGLED_FLESH)) + var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR))) // if we're bone only, all cutting attacks go straight to the bone - if(has_exterior && interior_ready_to_dismember) + if(!has_exterior && has_interior) if(wounding_type == WOUND_SLASH) wounding_type = WOUND_BLUNT - phantom_wounding_dmg *= (easy_dismember ? 1 : 0.6) + wounding_dmg *= (easy_dismember ? 1 : 0.6) else if(wounding_type == WOUND_PIERCE) wounding_type = WOUND_BLUNT - phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75) - if(exterior_ready_to_dismember && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)) - return + wounding_dmg *= (easy_dismember ? 1 : 0.75) else // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate // So a big sharp weapon is still all you need to destroy a limb - if(has_exterior && interior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_BONE) && sharpness) - playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100) + if(has_interior && exterior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_INTERIOR) && sharpness) if(wounding_type == WOUND_SLASH && !easy_dismember) - phantom_wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area + wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area if(wounding_type == WOUND_PIERCE && !easy_dismember) - phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated + wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated wounding_type = WOUND_BLUNT - else if(interior_ready_to_dismember && exterior_ready_to_dismember && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)) + if ((dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)) return - if (use_alternate_dismemberment_calc_even_if_mangleable || !can_theoretically_be_dismembered) - var/percent_to_total_max = (get_damage() / max_damage) - if (percent_to_total_max >= hp_percent_to_dismemberable) - if (try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)) - return - - return check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus) + return check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus) /** * check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria @@ -111,6 +70,8 @@ var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT)) var/injury_roll = base_roll injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus) + var/list/series_wounding_mods = check_series_wounding_mods() + if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100)) var/datum/wound/loss/dismembering = new dismembering.apply_dismember(src, woundtype, outright = TRUE, attack_direction = attack_direction) @@ -119,8 +80,8 @@ var/list/datum/wound/possible_wounds = list() for (var/datum/wound/type as anything in GLOB.all_wound_pregen_data) var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[type] - if (pregen_data.can_be_applied_to(src, woundtype, random_roll = TRUE)) - possible_wounds += type + if (pregen_data.can_be_applied_to(src, list(woundtype), random_roll = TRUE)) + possible_wounds[type] = pregen_data.get_weight(src, woundtype, damage, attack_direction, damage_source) // quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow if(ishuman(owner)) var/mob/living/carbon/human/human_wearer = owner @@ -131,39 +92,137 @@ bare_wound_bonus = 0 break - //cycle through the wounds of the relevant category from the most severe down - for(var/datum/wound/possible_wound as anything in possible_wounds) + for (var/datum/wound/iterated_path as anything in possible_wounds) + for (var/datum/wound/existing_wound as anything in wounds) + if (iterated_path == existing_wound.type) + possible_wounds -= iterated_path + break // breaks out of the nested loop + + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_path] + var/specific_injury_roll = (injury_roll + series_wounding_mods[pregen_data.wound_series]) + if (pregen_data.get_threshold_for(src, attack_direction, damage_source) > specific_injury_roll) + possible_wounds -= iterated_path + continue + + if (pregen_data.compete_for_wounding) + for (var/datum/wound/other_path as anything in possible_wounds) + if (other_path == iterated_path) + continue + if (initial(iterated_path.severity) == initial(other_path.severity) && pregen_data.overpower_wounds_of_even_severity) + possible_wounds -= other_path + continue + else if (pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_LESSERS) + if (initial(iterated_path.severity) > initial(other_path.severity)) + possible_wounds -= other_path + continue + else if (pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_GREATERS) + if (initial(iterated_path.severity) < initial(other_path.severity)) + possible_wounds -= other_path + continue + + while (length(possible_wounds)) + var/datum/wound/possible_wound = pick_weight(possible_wounds) + var/datum/wound_pregen_data/possible_pregen_data = GLOB.all_wound_pregen_data[possible_wound] + possible_wounds -= possible_wound + var/datum/wound/replaced_wound for(var/datum/wound/existing_wound as anything in wounds) - if(existing_wound.wound_series == initial(possible_wound.wound_series)) + var/datum/wound_pregen_data/existing_pregen_data = GLOB.all_wound_pregen_data[existing_wound.type] + if(existing_pregen_data.wound_series == possible_pregen_data.wound_series) if(existing_wound.severity >= initial(possible_wound.severity)) - return + continue else - replaced_wound = existing_wound // if we find something we keep iterating untilw e're done or we find we're outclassed by something in our series + replaced_wound = existing_wound + // if we get through this whole loop without continuing, we found our winner - if(initial(possible_wound.threshold_minimum) < injury_roll) - var/datum/wound/new_wound - if(replaced_wound) - new_wound = replaced_wound.replace_wound(new possible_wound, attack_direction = attack_direction) - else - new_wound = new possible_wound - new_wound.apply_wound(src, attack_direction = attack_direction, wound_source = damage_source) - 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 - return new_wound + var/datum/wound/new_wound = new possible_wound + if(replaced_wound) + new_wound = replaced_wound.replace_wound(new_wound, attack_direction = attack_direction) + else + new_wound.apply_wound(src, attack_direction = attack_direction, wound_source = damage_source) + 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 + 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 -/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE, wound_source) +/obj/item/bodypart/proc/force_wound_upwards(datum/wound/potential_wound, smited = FALSE, wound_source) SHOULD_NOT_OVERRIDE(TRUE) - var/datum/wound/potential_wound = specific_woundtype + if (isnull(potential_wound)) + return + + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[potential_wound] for(var/datum/wound/existing_wound as anything in wounds) - if (existing_wound.wound_series == initial(potential_wound.wound_series)) + var/datum/wound_pregen_data/existing_pregen_data = existing_wound.get_pregen_data() + if (existing_pregen_data.wound_series == pregen_data.wound_series) if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force existing_wound.replace_wound(new potential_wound, smited) return var/datum/wound/new_wound = new potential_wound new_wound.apply_wound(src, smited = smited, wound_source = wound_source) + return new_wound + +/** + * A simple proc to force a type of wound onto this mob. If you just want to force a specific mainline (fractures, bleeding, etc.) wound, you only need to care about the first 3 args. + * + * Args: + * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list. + * * obj/item/bodypart/limb: The limb we wil be applying the wound to. If null, a random bodypart will be picked. + * * min_severity: The minimum severity that will be considered. + * * max_severity: The maximum severity that will be considered. + * * severity_pick_mode: The "pick mode" to be used. See get_corresponding_wound_type's documentation + * * wound_source: The source of the wound to be applied. Nullable. + * + * For the rest of the args, refer to get_corresponding_wound_type(). + * + * Returns: + * A new wound instance if the application was successful, null otherwise. +*/ +/mob/living/carbon/proc/cause_wound_of_type_and_severity(wounding_type, obj/item/bodypart/limb, min_severity, max_severity = min_severity, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, wound_source) + if (isnull(limb)) + limb = pick(bodyparts) + + var/list/type_list = wounding_type + if (!islist(type_list)) + type_list = list(type_list) + + var/datum/wound/corresponding_typepath = get_corresponding_wound_type(type_list, limb, min_severity, max_severity, severity_pick_mode) + if (corresponding_typepath) + return limb.force_wound_upwards(corresponding_typepath, wound_source = wound_source) + +/// Limb is nullable, but picks a random one. Defers to limb.get_wound_threshold_of_wound_type, see it for documentation. +/mob/living/carbon/proc/get_wound_threshold_of_wound_type(wounding_type, severity, default, obj/item/bodypart/limb, wound_source) + if (isnull(limb)) + limb = pick(bodyparts) + + if (!limb) + return default + + return limb.get_wound_threshold_of_wound_type(wounding_type, severity, default, wound_source) + +/** + * A simple proc that gets the best wound to fit the criteria laid out, then returns its wound threshold. + * + * Args: + * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list of wounding_types. + * * severity: The severity that will be considered. + * * return_value_if_no_wound: If no wound is found, we will return this instead. (It is reccomended to use named args for this one, as its unclear what it is without) + * * wound_source: The theoretical source of the wound. Nullable. + * + * Returns: + * return_value_if_no_wound if no wound is found - if one IS found, the wound threshold for that wound. + */ +/obj/item/bodypart/proc/get_wound_threshold_of_wound_type(wounding_type, severity, return_value_if_no_wound, wound_source) + var/list/type_list = wounding_type + if (!islist(type_list)) + type_list = list(type_list) + + var/datum/wound/wound_path = get_corresponding_wound_type(type_list, src, severity, duplicates_allowed = TRUE, care_about_existing_wounds = FALSE) + if (wound_path) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_path] + return pregen_data.get_threshold_for(src, damage_source = wound_source) + + return return_value_if_no_wound /** * check_wounding_mods() is where we handle the various modifiers of a wound roll @@ -209,7 +268,20 @@ return injury_mod - /// Get whatever wound of the given type is currently attached to this limb, if any +/// Should return an assoc list of (wound_series -> penalty). Will be used in determining series-specific penalties for wounding. +/obj/item/bodypart/proc/check_series_wounding_mods() + RETURN_TYPE(/list) + + var/list/series_mods = list() + + for (var/datum/wound/iterated_wound as anything in wounds) + var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_wound.type] + + series_mods[pregen_data.wound_series] += iterated_wound.series_threshold_penalty + + return series_mods + +/// Get whatever wound of the given type is currently attached to this limb, if any /obj/item/bodypart/proc/get_wound_type(checking_type) RETURN_TYPE(checking_type) SHOULD_NOT_OVERRIDE(TRUE) diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm index 0838b2d18be..161492a726a 100644 --- a/code/modules/unit_tests/medical_wounds.dm +++ b/code/modules/unit_tests/medical_wounds.dm @@ -19,10 +19,11 @@ TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test") var/datum/wound/iter_test_wound + var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound] var/threshold_penalty = 0 for(iter_test_wound in iter_test_wound_list) - var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty if(dam_types[i] == BRUTE) tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) else if(dam_types[i] == BURN) @@ -59,10 +60,11 @@ TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test") var/datum/wound/iter_test_wound + var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound] var/threshold_penalty = 0 for(iter_test_wound in iter_test_wound_list) - var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty if(dam_types[i] == BRUTE) tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) else if(dam_types[i] == BURN) diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index f08e30a7554..1fd230bb47a 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -121,11 +121,11 @@ if(prob(35)) //Note: The randomstep on dump_mobs throws occupants into each other and often causes wounds regardless. continue for(var/obj/item/bodypart/head/head_to_wound as anything in carbon_occupant.bodyparts) - var/type_wound = pick(list( - /datum/wound/blunt/bone/moderate, - /datum/wound/blunt/bone/severe, - )) - head_to_wound.force_wound_upwards(type_wound, wound_source = src) + var/pick_mode = text2num(pick(list( + "[WOUND_PICK_LOWEST_SEVERITY]", + "[WOUND_PICK_HIGHEST_SEVERITY]" + ))) + carbon_occupant.cause_wound_of_type_and_severity(WOUND_BLUNT, head_to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, pick_mode) carbon_occupant.playsound_local(src, 'sound/weapons/flash_ring.ogg', 50) carbon_occupant.set_eye_blur_if_lower(rand(10 SECONDS, 20 SECONDS)) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index cfff03d1c48..5cc8d8c9419 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -954,13 +954,9 @@ return FALSE var/mob/living/carbon/carbon_target = atom_target for(var/obj/item/bodypart/squish_part in carbon_target.bodyparts) - var/type_wound - if (squish_part.biological_state & BIO_BONE) - type_wound = pick(list(/datum/wound/blunt/bone/critical, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/moderate)) - else - squish_part.receive_damage(brute=30) - if (type_wound) - squish_part.force_wound_upwards(type_wound, wound_source = "crushed by [src]") + var/severity = pick(WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL) + if (!carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, squish_part, severity, wound_source = "crushed by [src]")) + squish_part.receive_damage(brute = 30) carbon_target.visible_message(span_danger("[carbon_target]'s body is maimed underneath the mass of [src]!"), span_userdanger("Your body is maimed underneath the mass of [src]!")) return TRUE if(CRUSH_CRIT_HEADGIB) // skull squish! diff --git a/strings/wounds/bone_scar_desc.json b/strings/wounds/bone_scar_desc.json index b1eb84bb8b7..2a89f022002 100644 --- a/strings/wounds/bone_scar_desc.json +++ b/strings/wounds/bone_scar_desc.json @@ -1,6 +1,11 @@ { "generic": ["general disfigurement"], + "dislocate": [ + "the bone equivalent of a faded bruise", + "a series of tiny chip marks" + ], + "bluntmoderate": [ "the bone equivalent of a faded bruise", "a series of tiny chip marks" diff --git a/strings/wounds/flesh_scar_desc.json b/strings/wounds/flesh_scar_desc.json index d8c253873cc..0fd78bec8e4 100644 --- a/strings/wounds/flesh_scar_desc.json +++ b/strings/wounds/flesh_scar_desc.json @@ -1,6 +1,12 @@ { "generic": ["general disfigurement"], + "dislocate": [ + "light discoloring", + "a slight blue tint", + "a slightly deadened tint" + ], + "bluntmoderate": [ "light discoloring", "a slight blue tint", diff --git a/tgstation.dme b/tgstation.dme index 4655bfb6df8..d0394173e73 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2480,6 +2480,7 @@ #include "code\modules\actionspeed\modifiers\drugs.dm" #include "code\modules\actionspeed\modifiers\mood.dm" #include "code\modules\actionspeed\modifiers\status_effects.dm" +#include "code\modules\actionspeed\modifiers\wound.dm" #include "code\modules\admin\admin.dm" #include "code\modules\admin\admin_fax_panel.dm" #include "code\modules\admin\admin_investigate.dm"