diff --git a/_maps/shuttles/emergency_luxury.dmm b/_maps/shuttles/emergency_luxury.dmm index 15ee3cbb1054..ec1a1f1a266f 100644 --- a/_maps/shuttles/emergency_luxury.dmm +++ b/_maps/shuttles/emergency_luxury.dmm @@ -78,18 +78,6 @@ /obj/structure/table/wood/fancy/black, /turf/open/floor/carpet, /area/shuttle/escape/luxury) -"cZ" = ( -/obj/machinery/door/poddoor/shutters{ - id = "ohnopoors" - }, -/obj/structure/window/plastitanium{ - desc = "A durable looking window made of an alloy of of plasma and titanium. Appears to have poor people tears smudged all over it."; - flags_1 = 2177; - resistance_flags = 64 - }, -/obj/structure/grille/indestructable, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) "dl" = ( /obj/effect/decal/cleanable/dirt/dust, /obj/effect/decal/cleanable/dirt/dust, @@ -233,6 +221,18 @@ /obj/structure/shuttle/engine/propulsion, /turf/open/floor/plating/airless, /area/shuttle/escape/luxury) +"oo" = ( +/obj/machinery/door/poddoor/shutters{ + id = "ohnopoors" + }, +/obj/structure/grille/indestructable, +/obj/structure/window/plastitanium{ + desc = "A durable looking window made of an alloy of of plasma and titanium. Appears to have poor people tears smudged all over it."; + flags_1 = 2177; + resistance_flags = 64 + }, +/turf/open/floor/plating, +/area/shuttle/escape/luxury) "or" = ( /obj/structure/shuttle/engine/heater, /obj/structure/window/reinforced{ @@ -283,7 +283,7 @@ /area/shuttle/escape/luxury) "pv" = ( /obj/structure/table/glass, -/obj/item/surgicaldrill/advanced, +/obj/item/cautery/advanced, /obj/item/scalpel/advanced, /obj/item/retractor/advanced, /turf/open/floor/mineral/gold{ @@ -559,16 +559,16 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/plating, /area/shuttle/escape/luxury) -"IL" = ( +"JI" = ( /obj/machinery/door/poddoor/shutters{ id = "ohnopoors" }, -/obj/structure/grille/indestructable, /obj/structure/window/plastitanium{ desc = "A durable looking window made of an alloy of of plasma and titanium. Appears to have poor people tears smudged all over it."; flags_1 = 2177; resistance_flags = 64 }, +/obj/structure/grille/indestructable, /turf/open/floor/plating, /area/shuttle/escape/luxury) "Kn" = ( @@ -968,15 +968,15 @@ eL eL eL mt -IL -IL -IL -IL -IL -IL -IL -IL -cZ +oo +oo +oo +oo +oo +oo +oo +oo +JI mt mt mt diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index 9f96358b45be..68e610aaeb65 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -120,23 +120,28 @@ #define MUTCOLORS_PARTSONLY 8 #define NOZOMBIE 9 /// If we want a race to have a standard color (for now this is only polysmorphs) -#define NOCOLORCHANGE 20 +#define NOCOLORCHANGE 10 /// Uses weird leg sprites. Optional for Lizards, required for ashwalkers. Don't give it to other races unless you make sprites for this (see human_parts_greyscale.dmi) -#define DIGITIGRADE 10 -#define NO_UNDERWEAR 11 -#define NOLIVER 12 -#define NOSTOMACH 13 -#define NO_DNA_COPY 14 -#define DRINKSBLOOD 15 -#define NOFLASH 16 +#define DIGITIGRADE 11 +#define NO_UNDERWEAR 12 +#define NOLIVER 13 +#define NOSTOMACH 14 +#define NO_DNA_COPY 15 +#define DRINKSBLOOD 16 +#define NOFLASH 17 /// Use this if you want to change the race's color without the player being able to pick their own color. AKA special color shifting -#define DYNCOLORS 17 +#define DYNCOLORS 18 /// Forced genders -#define AGENDER 18 -#define FGENDER 21 -#define MGENDER 22 +#define AGENDER 19 +#define FGENDER 20 +#define MGENDER 21 /// Do not draw eyes or eyeless overlay -#define NOEYESPRITES 19 +#define NOEYESPRITES 22 +/// Used for determining which wounds are applicable to this species. +/// if we have flesh (can suffer slash/piercing/burn wounds, requires they don't have NOBLOOD) +#define HAS_FLESH 23 +/// if we have bones (can suffer bone wounds) +#define HAS_BONE 24 //organ slots #define ORGAN_SLOT_BRAIN "brain" diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index 7f619bde73d9..d83394236f4f 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -79,6 +79,10 @@ #define ADMIN_PUNISHMENT_WHISTLE "Whistle" #define ADMIN_PUNISHMENT_CLUWNE "Cluwne" #define ADMIN_PUNISHMENT_MCNUGGET "Nugget" +#define ADMIN_PUNISHMENT_CRACK ":B:oneless" +#define ADMIN_PUNISHMENT_BLEED ":B:loodless" +#define ADMIN_PUNISHMENT_PERFORATE ":B:erforate" +#define ADMIN_PUNISHMENT_SCARIFY "Scarify" #define AHELP_ACTIVE 1 #define AHELP_CLOSED 2 diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 800a91066362..c83489125e41 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -170,9 +170,9 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define REFLECT_FAKEPROJECTILE (1<<1) //Object/Item sharpness -#define IS_BLUNT 0 -#define IS_SHARP 1 -#define IS_SHARP_ACCURATE 2 +#define SHARP_NONE 0 +#define SHARP_EDGED 1 +#define SHARP_POINTY 2 //His Grace. #define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 93ada4e1f53d..3eb482c4c83a 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -71,6 +71,7 @@ #define COMSIG_ATOM_HULK_ATTACK "hulk_attack" //from base of atom/attack_hulk(): (/mob/living/carbon/human) #define COMSIG_PARENT_EXAMINE "atom_examine" //from base of atom/examine(): (/mob) #define COMSIG_ATOM_GET_EXAMINE_NAME "atom_examine_name" //from base of atom/get_examine_name(): (/mob, list/overrides) +#define COMSIG_PARENT_EXAMINE_MORE "atom_examine_more" //from base of atom/examine_more(): (/mob) //Positions for overrides list #define EXAMINE_POSITION_ARTICLE 1 #define EXAMINE_POSITION_BEFORE 2 @@ -228,9 +229,17 @@ #define COMSIG_LIVING_STATUS_SLEEP "living_sleeping" //from base of mob/living/Sleeping() (amount, update, ignore) #define COMPONENT_NO_STUN 1 //For all of them -// /mob/living/carbon signals +// /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) +///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment +#define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb" + #define COMPONENT_NO_ATTACH (1<<0) +#define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" //from base of /obj/item/bodypart/proc/drop_limb(special, dismembered) #define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity)) #define COMSIG_CARBON_STATUS_STAMCRIT "living_stamcrit" //from base of mob/living/carbon/enter_stamcrit() +#define COMSIG_BODYPART_GAUZED "bodypart_gauzed" // from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze) +#define COMSIG_BODYPART_GAUZE_DESTROYED "bodypart_degauzed" // from [/obj/item/bodypart/proc/seep_gauze] when it runs out of absorption // /mob/living/simple_animal/hostile signals #define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" @@ -299,6 +308,7 @@ // /mob/living/carbon/human signals +#define COMSIG_HUMAN_EARLY_UNARMED_ATTACK "human_early_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity, modifiers) #define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target) #define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker) #define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 01d22a59a944..e59036b42259 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -225,14 +225,6 @@ GLOBAL_LIST_INIT(heavyfootmob, typecacheof(list( #define iscash(A) (istype(A, /obj/item/coin) || istype(A, /obj/item/stack/spacecash) || istype(A, /obj/item/holochip)) -GLOBAL_LIST_INIT(pointed_types, typecacheof(list( - /obj/item/pen, - /obj/item/screwdriver, - /obj/item/reagent_containers/syringe, - /obj/item/kitchen/fork))) - -#define is_pointed(W) (is_type_in_typecache(W, GLOB.pointed_types)) - #define isbodypart(A) (istype(A, /obj/item/bodypart)) #define isprojectile(A) (istype(A, /obj/item/projectile)) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 5f25a72b22f2..c5ab6c0f1124 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -407,6 +407,9 @@ GLOBAL_LIST_INIT(pda_styles, list(MONO, VT, ORBITRON, SHARE)) #define PIRATE_NAMES_FILE "pirates.json" #define REDPILL_FILE "redpill.json" #define WANTED_FILE "wanted_message.json" +#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json" +#define BONE_SCAR_FILE "wounds/bone_scar_desc.json" +#define SCAR_LOC_FILE "wounds/scar_loc.json" //Fullscreen overlay resolution in tiles. #define FULLSCREEN_OVERLAY_RESOLUTION_X 15 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index f395097ab12c..9d544b09ffad 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -84,10 +84,6 @@ #define BODYPART_ORGANIC 1 #define BODYPART_ROBOTIC 2 -#define BODYPART_NOT_DISABLED 0 -#define BODYPART_DISABLED_DAMAGE 1 -#define BODYPART_DISABLED_PARALYSIS 2 - #define DEFAULT_BODYPART_ICON_ORGANIC 'icons/mob/human_parts_greyscale.dmi' #define DEFAULT_BODYPART_ICON_ROBOTIC 'icons/mob/augmentation/augments.dmi' @@ -133,12 +129,14 @@ #define TRAUMA_RESILIENCE_BASIC 1 //Curable with chems #define TRAUMA_RESILIENCE_SURGERY 2 //Curable with brain surgery #define TRAUMA_RESILIENCE_LOBOTOMY 3 //Curable with lobotomy -#define TRAUMA_RESILIENCE_MAGIC 4 //Curable only with magic -#define TRAUMA_RESILIENCE_ABSOLUTE 5 //This is here to stay +#define TRAUMA_RESILIENCE_WOUND 4 //Curable by healing the head wound +#define TRAUMA_RESILIENCE_MAGIC 5 //Curable only with magic +#define TRAUMA_RESILIENCE_ABSOLUTE 6 //This is here to stay //Limit of traumas for each resilience tier #define TRAUMA_LIMIT_BASIC 3 #define TRAUMA_LIMIT_SURGERY 2 +#define TRAUMA_LIMIT_WOUND 2 #define TRAUMA_LIMIT_LOBOTOMY 3 #define TRAUMA_LIMIT_MAGIC 3 #define TRAUMA_LIMIT_ABSOLUTE INFINITY @@ -340,4 +338,6 @@ #define WABBAJACK (1<<6) #define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return; -#define INTERACTING_WITH(X, Y) (Y in X.do_afters) \ No newline at end of file +/// If you examine the same atom twice in this timeframe, we call examine_more() instead of examine() +#define EXAMINE_MORE_TIME 1 SECONDS +#define INTERACTING_WITH(X, Y) (Y in X.do_afters) diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index c5742f020f19..e4015e6ae6d0 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -29,6 +29,7 @@ #define SURGICAL_TOOL (1<<12) //Tool commonly used for surgery: won't attack targets in an active surgical operation on help intent (in case of mistakes) #define UNCATCHABLE (1<<13) // Makes any item uncatchable if it is thrown at them #define MEDRESIST (1<<14) // This item will block medical sprays when worn +#define HAND_ITEM (1<<15) // If an item is just your hand (circled hand, slapper) and shouldn't block things like riding // Flags for the clothing_flags var on /obj/item/clothing @@ -54,3 +55,8 @@ /// Flags for the pod_flags var on /obj/structure/closet/supplypod #define FIRST_SOUNDS (1<<0) // If it shouldn't play sounds the first time it lands, used for reverse mode + +/// Integrity defines for clothing (not flags but close enough) +#define CLOTHING_PRISTINE 0 // We have no damage on the clothing +#define CLOTHING_DAMAGED 1 // There's some damage on the clothing but it still has at least one functioning bodypart and can be equipped +#define CLOTHING_SHREDDED 2 // The clothing is useless and cannot be equipped unless repaired first \ No newline at end of file diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 1f7ce9f19d5e..ded8343df597 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -42,7 +42,9 @@ #define STATUS_EFFECT_CREEP /datum/status_effect/creep //Provides immunity to lightburn for darkspawn, does nothing to anyone else //Yogs - #define STATUS_EFFECT_TIME_DILATION /datum/status_effect/time_dilation //Provides immunity to slowdown and halves click-delay/action times //Yogs +#define STATUS_EFFECT_TIME_DILATION /datum/status_effect/time_dilation //Provides immunity to slowdown and halves click-delay/action times //Yogs + +#define STATUS_EFFECT_DETERMINED /datum/status_effect/determined //currently in a combat high from being seriously wounded ///////////// // DEBUFFS // @@ -109,6 +111,8 @@ #define STATUS_EFFECT_CLOUDSTRUCK /datum/status_effect/cloudstruck //blinds and applies an overlay. +#define STATUS_EFFECT_LIMP /datum/status_effect/limp //For when you have a busted leg (or two!) and want additional slowdown when walking on that leg + ///////////// // NEUTRAL // ///////////// diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm index d028041f5a5a..8f03c38cc773 100644 --- a/code/__DEFINES/tools.dm +++ b/code/__DEFINES/tools.dm @@ -10,6 +10,13 @@ #define TOOL_SHOVEL "shovel" #define TOOL_HATCHET "hatchet" +#define TOOL_RETRACTOR "retractor" +#define TOOL_HEMOSTAT "hemostat" +#define TOOL_CAUTERY "cautery" +#define TOOL_DRILL "drill" +#define TOOL_SCALPEL "scalpel" +#define TOOL_SAW "saw" +#define TOOL_BONESET "bonesetter" // If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY, // tool sound is only played when op is started. If not, it's played twice. diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 7a14f4c6b430..02f3f69fc3c2 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -1,3 +1,6 @@ +#define SIGNAL_ADDTRAIT(trait_ref) "addtrait [trait_ref]" +#define SIGNAL_REMOVETRAIT(trait_ref) "removetrait [trait_ref]" + // trait accessor defines #define ADD_TRAIT(target, trait, source) \ do { \ @@ -6,12 +9,14 @@ target.status_traits = list(); \ _L = target.status_traits; \ _L[trait] = list(source); \ + SEND_SIGNAL(target, SIGNAL_ADDTRAIT(trait), trait); \ } else { \ _L = target.status_traits; \ if (_L[trait]) { \ _L[trait] |= list(source); \ } else { \ _L[trait] = list(source); \ + SEND_SIGNAL(target, SIGNAL_ADDTRAIT(trait), trait); \ } \ } \ } while (0) @@ -31,13 +36,38 @@ } \ };\ if (!length(_L[trait])) { \ - _L -= trait \ + _L -= trait; \ + SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(trait), trait); \ }; \ if (!length(_L)) { \ target.status_traits = null \ }; \ } \ } while (0) +#define REMOVE_TRAIT_NOT_FROM(target, trait, sources) \ + do { \ + var/list/_traits_list = target.status_traits; \ + var/list/_sources_list; \ + if (sources && !islist(sources)) { \ + _sources_list = list(sources); \ + } else { \ + _sources_list = sources\ + }; \ + if (_traits_list && _traits_list[trait]) { \ + for (var/_trait_source in _traits_list[trait]) { \ + if (!(_trait_source in _sources_list)) { \ + _traits_list[trait] -= _trait_source \ + } \ + };\ + if (!length(_traits_list[trait])) { \ + _traits_list -= trait; \ + SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(trait), trait); \ + }; \ + if (!length(_traits_list)) { \ + target.status_traits = null \ + }; \ + } \ + } while (0) #define REMOVE_TRAITS_NOT_IN(target, sources) \ do { \ var/list/_L = target.status_traits; \ @@ -46,11 +76,36 @@ for (var/_T in _L) { \ _L[_T] &= _S;\ if (!length(_L[_T])) { \ - _L -= _T } \ + _L -= _T; \ + SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(_T), _T); \ + }; \ };\ - if (!length(_L)) { \ - target.status_traits = null\ + if (!length(_L)) { \ + target.status_traits = null\ + };\ + }\ + } while (0) + +#define REMOVE_TRAITS_IN(target, sources) \ + do { \ + var/list/_L = target.status_traits; \ + var/list/_S = sources; \ + if (sources && !islist(sources)) { \ + _S = list(sources); \ + } else { \ + _S = sources\ + }; \ + if (_L) { \ + for (var/_T in _L) { \ + _L[_T] -= _S;\ + if (!length(_L[_T])) { \ + _L -= _T; \ + SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(_T)); \ + }; \ };\ + if (!length(_L)) { \ + target.status_traits = null\ + };\ }\ } while (0) #define HAS_TRAIT(target, trait) (target.status_traits ? (target.status_traits[trait] ? TRUE : FALSE) : FALSE) @@ -105,7 +160,8 @@ #define TRAIT_EASYDISMEMBER "easy_dismember" #define TRAIT_LIMBATTACHMENT "limb_attach" #define TRAIT_NOLIMBDISABLE "no_limb_disable" -#define TRAIT_EASYLIMBDISABLE "easy_limb_disable" +#define TRAIT_EASILY_WOUNDED "easy_limb_wound" +#define TRAIT_HARDLY_WOUNDED "hard_limb_wound" #define TRAIT_TOXINLOVER "toxinlover" #define TRAIT_NOBREATH "no_breath" #define TRAIT_ANTIMAGIC "anti_magic" @@ -158,9 +214,14 @@ #define TRAIT_SURGERY_PREPARED "surgery_prepared" #define TRAIT_NO_PASSIVE_COOLING "no-passive-cooling" #define TRAIT_NO_PASSIVE_HEATING "no-passive-heating" +#define TRAIT_BLOODY_MESS "bloody_mess" //from heparin, makes open bleeding wounds rapidly spill more blood +#define TRAIT_COAGULATING "coagulating" //from coagulant reagents, this doesn't affect the bleeding itself but does affect the bleed warning messages //non-mob traits -#define TRAIT_PARALYSIS "paralysis" //Used for limb-based paralysis, where replacing the limb will fix it +/// Used for limb-based paralysis, where replacing the limb will fix it. +#define TRAIT_PARALYSIS "paralysis" +/// Used for limbs. +#define TRAIT_DISABLED_BY_WOUND "disabled-by-wound" // item traits #define TRAIT_NODROP "nodrop" diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm new file mode 100644 index 000000000000..08cb5191b830 --- /dev/null +++ b/code/__DEFINES/wounds.dm @@ -0,0 +1,153 @@ + +// ~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 + +/// any damage dealt over this is ignored for damage rolls unless the target has the frail quirk (35^1.4=145, for reference) +#define WOUND_MAX_CONSIDERED_DAMAGE 35 +/// an attack must do this much damage after armor in order to roll for being a wound (incremental pressure damage need not apply) +#define WOUND_MINIMUM_DAMAGE 5 +/// an attack must do this much damage after armor in order to be eliigible to dismember a suitably mushed bodypart +#define DISMEMBER_MINIMUM_DAMAGE 10 +/// If an attack rolls this high with their wound (including mods), we try to outright dismember the limb. Note 250 is high enough that with a perfect max roll of 145 (see max cons'd damage), you'd need +100 in mods to do this +#define WOUND_DISMEMBER_OUTRIGHT_THRESH 250 +/// set wound_bonus on an item or attack to this to disable checking wounding for the attack +#define CANT_WOUND -100 + +// ~wound severities +/// for jokey/meme wounds like stubbed toe, no standard messages/sounds or second winds +#define WOUND_SEVERITY_TRIVIAL 0 +#define WOUND_SEVERITY_MODERATE 1 +#define WOUND_SEVERITY_SEVERE 2 +#define WOUND_SEVERITY_CRITICAL 3 +/// outright dismemberment of limb +#define WOUND_SEVERITY_LOSS 4 + +// ~wound categories +/// any brute weapon/attack that doesn't have sharpness. rolls for blunt bone wounds +#define WOUND_BLUNT 1 +/// any brute weapon/attack with sharpness = SHARP_EDGED. rolls for slash wounds +#define WOUND_SLASH 2 +/// any brute weapon/attack with sharpness = SHARP_POINTY. rolls for piercing wounds +#define WOUND_PIERCE 3 +/// any concentrated burn attack (lasers really). rolls for burning wounds +#define WOUND_BURN 4 + + +// ~determination second wind defines +// How much determination reagent to add each time someone gains a new wound in [/datum/wound/proc/second_wind] +#define WOUND_DETERMINATION_MODERATE 1 +#define WOUND_DETERMINATION_SEVERE 2.5 +#define WOUND_DETERMINATION_CRITICAL 5 +#define WOUND_DETERMINATION_LOSS 7.5 + +/// the max amount of determination you can have +#define WOUND_DETERMINATION_MAX 10 + +/// While someone has determination in their system, their bleed rate is slightly reduced +#define WOUND_DETERMINATION_BLEED_MOD 0.85 + +// ~wound global lists +// list in order of highest severity to lowest +GLOBAL_LIST_INIT(global_wound_types, list(WOUND_BLUNT = list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate), + WOUND_SLASH = list(/datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate), + WOUND_PIERCE = list(/datum/wound/pierce/critical, /datum/wound/pierce/severe, /datum/wound/pierce/moderate), + WOUND_BURN = list(/datum/wound/burn/critical, /datum/wound/burn/severe, /datum/wound/burn/moderate) + )) + +// every single type of wound that can be rolled naturally, in case you need to pull a random one +GLOBAL_LIST_INIT(global_all_wound_types, list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate, + /datum/wound/slash/critical, /datum/wound/slash/severe, /datum/wound/slash/moderate, + /datum/wound/pierce/critical, /datum/wound/pierce/severe, /datum/wound/pierce/moderate, + /datum/wound/burn/critical, /datum/wound/burn/severe, /datum/wound/burn/moderate)) + +// ~burn wound infection defines +// Thresholds for infection for burn wounds, once infestation hits each threshold, things get steadily worse +/// below this has no ill effects from infection +#define WOUND_INFECTION_MODERATE 4 +/// then below here, you ooze some pus and suffer minor tox damage, but nothing serious +#define WOUND_INFECTION_SEVERE 8 +/// then below here, your limb occasionally locks up from damage and infection and briefly becomes disabled. Things are getting really bad +#define WOUND_INFECTION_CRITICAL 12 +/// below here, your skin is almost entirely falling off and your limb locks up more frequently. You are within a stone's throw of septic paralysis and losing the limb +#define WOUND_INFECTION_SEPTIC 20 +// above WOUND_INFECTION_SEPTIC, your limb is completely putrid and you start rolling to lose the entire limb by way of paralyzation. After 3 failed rolls (~4-5% each probably), the limb is paralyzed + +// ~random wound balance defines +/// how quickly sanitization removes infestation and decays per tick +#define WOUND_BURN_SANITIZATION_RATE 0.15 +/// how much blood you can lose per tick per slash max. 8 is a LOT of blood for one cut so don't worry about hitting it easily +#define WOUND_SLASH_MAX_BLOODFLOW 8 +/// dead people don't bleed, but they can clot! this is the minimum amount of clotting per tick on dead people, so even critical cuts will slowly clot in dead people +#define WOUND_SLASH_DEAD_CLOT_MIN 0.05 +/// if we suffer a bone wound to the head that creates brain traumas, the timer for the trauma cycle is +/- by this percent (0-100) +#define WOUND_BONE_HEAD_TIME_VARIANCE 20 + + +// ~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. Skin 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 +#define BODYPART_MANGLED_NONE 0 +#define BODYPART_MANGLED_BONE 1 +#define BODYPART_MANGLED_FLESH 2 +#define BODYPART_MANGLED_BOTH 3 + +// ~biology defines +// What kind of biology we have, and what wounds we can suffer, mostly relies on the HAS_FLESH and HAS_BONE species traits on human species +/// golems and androids, cannot suffer any wounds +#define BIO_INORGANIC 0 +/// skeletons and plasmemes, can only suffer bone wounds, only needs mangled bone to be able to dismember +#define BIO_JUST_BONE 1 +/// nothing right now, maybe slimepeople in the future, can only suffer slashing, piercing, and burn wounds +#define BIO_JUST_FLESH 2 +/// standard humanoids, can suffer all wounds, needs mangled bone and flesh to dismember. conveniently, what you get when you combine BIO_JUST_BONE and BIO_JUST_FLESH +#define BIO_FLESH_BONE 3 + +// ~wound flag defines +/// If this wound requires having the HAS_FLESH flag for humanoids +#define FLESH_WOUND (1<<0) +/// If this wound requires having the HAS_BONE flag for humanaoids +#define BONE_WOUND (1<<1) +/// If having this wound counts as mangled flesh for dismemberment +#define MANGLES_FLESH (1<<2) +/// If having this wound counts as mangled bone for dismemberment +#define MANGLES_BONE (1<<3) +/// If this wound marks the limb as being allowed to have gauze applied +#define ACCEPTS_GAUZE (1<<4) + +// ~scar persistence defines +// The following are the order placements for persistent scar save formats +/// The version number of the scar we're saving, any scars being loaded below this number will be discarded, see SCAR_CURRENT_VERSION below +#define SCAR_SAVE_VERS 1 +/// The body_zone we're applying to on granting +#define SCAR_SAVE_ZONE 2 +/// The description we're loading +#define SCAR_SAVE_DESC 3 +/// The precise location we're loading +#define SCAR_SAVE_PRECISE_LOCATION 4 +/// The severity the scar had +#define SCAR_SAVE_SEVERITY 5 +/// Whether this is a BIO_JUST_BONE scar, a BIO_JUST_FLESH scar, or a BIO_FLESH_BONE scar (so you can't load fleshy human scars on a plasmaman character) +#define SCAR_SAVE_BIOLOGY 6 +/// Which character slot this was saved to +#define SCAR_SAVE_CHAR_SLOT 7 +///how many fields we save for each scar (so the number of above fields) +#define SCAR_SAVE_LENGTH 7 + +/// saved scars with a version lower than this will be discarded, increment when you update the persistent scarring format in a way that invalidates previous saved scars (new fields, reordering, etc) +#define SCAR_CURRENT_VERSION 3 +/// how many scar slots, per character slot, we have to cycle through for persistent scarring, if enabled in character prefs +#define PERSISTENT_SCAR_SLOTS 3 + +// ~blood_flow rates of change, these are used by [/datum/wound/proc/get_bleed_rate_of_change] from [/mob/living/carbon/proc/bleed_warn] to let the player know if their bleeding is getting better/worse/the same +/// Our wound is clotting and will eventually stop bleeding if this continues +#define BLOOD_FLOW_DECREASING -1 +/// Our wound is bleeding but is holding steady at the same rate. +#define BLOOD_FLOW_STEADY 0 +/// Our wound is bleeding and actively getting worse, like if we're a critical slash or if we're afflicted with heparin +#define BLOOD_FLOW_INCREASING 1 + +/// How often can we annoy the player about their bleeding? This duration is extended if it's not serious bleeding +#define BLEEDING_MESSAGE_BASE_CD 10 SECONDS \ No newline at end of file diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 1bbae3e42ac2..2338c3380280 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -92,6 +92,10 @@ if (CONFIG_GET(flag/log_attack)) WRITE_LOG(GLOB.world_attack_log, "ATTACK: [text]") +/proc/log_wounded(text) + if (CONFIG_GET(flag/log_attack)) + WRITE_LOG(GLOB.world_attack_log, "WOUND: [text]") + /proc/log_manifest(ckey, datum/mind/mind,mob/body, latejoin = FALSE) if (CONFIG_GET(flag/log_manifest)) WRITE_LOG(GLOB.world_manifest_log, "[ckey] \\ [body.real_name] \\ [mind.assigned_role] \\ [mind.special_role ? mind.special_role : "NONE"] \\ [latejoin ? "LATEJOIN":"ROUNDSTART"]") diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index 258e767d4ab9..34c9a43238ac 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -339,10 +339,24 @@ /proc/isLeap(y) return ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0)) - +/// For finding out what body parts a body zone covers, the inverse of the below basically +/proc/zone2body_parts_covered(def_zone) + switch(def_zone) + if(BODY_ZONE_CHEST) + return list(CHEST, GROIN) + if(BODY_ZONE_HEAD) + return list(HEAD) + if(BODY_ZONE_L_ARM) + return list(ARM_LEFT, HAND_LEFT) + if(BODY_ZONE_R_ARM) + return list(ARM_RIGHT, HAND_RIGHT) + if(BODY_ZONE_L_LEG) + return list(LEG_LEFT, FOOT_LEFT) + if(BODY_ZONE_R_LEG) + return list(LEG_RIGHT, FOOT_RIGHT) //Turns a Body_parts_covered bitfield into a list of organ/limb names. -//(I challenge you to find a use for this) +//(I challenge you to find a use for this)-I found a use for it!! /proc/body_parts_covered2organ_names(bpc) var/list/covered_parts = list() diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 895f1dd14714..70e361f48d2e 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -755,12 +755,10 @@ GLOBAL_LIST_INIT(can_embed_types, typecacheof(list( /proc/can_embed(obj/item/W) if(W.is_sharp()) - return 1 - if(is_pointed(W)) - return 1 + return TRUE if(is_type_in_typecache(W, GLOB.can_embed_types)) - return 1 + return TRUE /* diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 0ab7acec900f..3c9e05a00849 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -42,6 +42,7 @@ GLOBAL_LIST_INIT(bitfields, list( "BEING_REMOVED" = BEING_REMOVED, "IN_INVENTORY" = IN_INVENTORY, "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, + "HAND_ITEM" = HAND_ITEM, "NEEDS_PERMIT" = NEEDS_PERMIT, "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index db1abd0a5e4c..ea429d1e4fc8 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -71,8 +71,14 @@ if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, "You don't want to harm other living beings!") return TRUE - + if((item_flags & SURGICAL_TOOL) && (user.a_intent != INTENT_HARM)) // checks for if harm intent with surgery tool + if(iscarbon(M)) + var/mob/living/carbon/C = M + for(var/i in C.all_wounds) + var/datum/wound/W = i + if(W.try_treating(src, user)) + return TRUE to_chat(user, "You aren't doing surgery!") //yells at you return TRUE @@ -144,9 +150,9 @@ else return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100 -/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area) +/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area, obj/item/bodypart/hit_bodypart) var/message_verb = "attacked" - if(I.attack_verb && I.attack_verb.len) + if(length(I.attack_verb)) message_verb = "[pick(I.attack_verb)]" else if(!I.force) return diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 36c80e15b0a6..6264203f9ee9 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -1,16 +1,19 @@ /* - Humans: + Humans: Adds an exception for gloves, to allow special glove types like the ninja ones. Otherwise pretty standard. */ /mob/living/carbon/human/UnarmedAttack(atom/A, proximity) - if(HAS_TRAIT(A, TRAIT_NOINTERACT)) to_chat(A, "You can't touch things!") return if(!has_active_hand()) //can't attack without a hand. + var/obj/item/bodypart/check_arm = get_active_hand() + if(check_arm?.bodypart_disabled) + to_chat(src, "Your [check_arm.name] is in no condition to be used.") + return to_chat(src, "You look at your arm and sigh.") return @@ -22,10 +25,9 @@ return var/override = 0 - + override = SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, A) & COMPONENT_NO_ATTACK_HAND for(var/datum/mutation/human/HM in dna.mutations) override += HM.on_attack_hand(A, proximity) - if(override) return diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm index a4f4a158411f..7e50c9eb10c8 100644 --- a/code/controllers/subsystem/persistence.dm +++ b/code/controllers/subsystem/persistence.dm @@ -152,6 +152,7 @@ SUBSYSTEM_DEF(persistence) CollectAntagReputation() SaveRandomizedRecipes() SavePaintings() + SaveScars() /datum/controller/subsystem/persistence/proc/GetPhotoAlbums() var/album_path = file("data/photo_albums.json") @@ -345,4 +346,16 @@ SUBSYSTEM_DEF(persistence) var/json_file = file("data/paintings.json") fdel(json_file) - WRITE_FILE(json_file, json_encode(paintings)) \ No newline at end of file + WRITE_FILE(json_file, json_encode(paintings)) + +/datum/controller/subsystem/persistence/proc/SaveScars() + for(var/i in GLOB.joined_player_list) + var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i) + if(!istype(ending_human) || !ending_human.mind?.original_character_slot_index || !ending_human.client || !ending_human.client.prefs || !ending_human.client.prefs.persistent_scars) + continue + + var/mob/living/carbon/human/original_human = ending_human.mind.original_character + if(!original_human || original_human.stat == DEAD || !original_human.all_scars || original_human != ending_human) + original_human.save_persistent_scars(TRUE) + else + original_human.save_persistent_scars() \ No newline at end of file diff --git a/code/datums/armor.dm b/code/datums/armor.dm index cbf4b76c60fb..4acdb5baaa78 100644 --- a/code/datums/armor.dm +++ b/code/datums/armor.dm @@ -1,9 +1,9 @@ -#define ARMORID "armor-[melee]-[bullet]-[laser]-[energy]-[bomb]-[bio]-[rad]-[fire]-[acid]-[magic]" +#define ARMORID "armor-[melee]-[bullet]-[laser]-[energy]-[bomb]-[bio]-[rad]-[fire]-[acid]-[magic]-[wound]" -/proc/getArmor(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0) +/proc/getArmor(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, wound = 0) . = locate(ARMORID) if (!.) - . = new /datum/armor(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic) + . = new /datum/armor(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic, wound) /datum/armor datum_flags = DF_USE_TAG @@ -17,8 +17,9 @@ var/fire var/acid var/magic + var/wound -/datum/armor/New(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0) +/datum/armor/New(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, wound = 0) src.melee = melee src.bullet = bullet src.laser = laser @@ -29,15 +30,16 @@ src.fire = fire src.acid = acid src.magic = magic + src.wound = wound tag = ARMORID -/datum/armor/proc/modifyRating(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0) - return getArmor(src.melee+melee, src.bullet+bullet, src.laser+laser, src.energy+energy, src.bomb+bomb, src.bio+bio, src.rad+rad, src.fire+fire, src.acid+acid, src.magic+magic) +/datum/armor/proc/modifyRating(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, wound = 0) + return getArmor(src.melee+melee, src.bullet+bullet, src.laser+laser, src.energy+energy, src.bomb+bomb, src.bio+bio, src.rad+rad, src.fire+fire, src.acid+acid, src.magic+magic, src.wound+wound) /datum/armor/proc/modifyAllRatings(modifier = 0) - return getArmor(melee+modifier, bullet+modifier, laser+modifier, energy+modifier, bomb+modifier, bio+modifier, rad+modifier, fire+modifier, acid+modifier, magic+modifier) + return getArmor(melee+modifier, bullet+modifier, laser+modifier, energy+modifier, bomb+modifier, bio+modifier, rad+modifier, fire+modifier, acid+modifier, magic+modifier, wound+modifier) -/datum/armor/proc/setRating(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic) +/datum/armor/proc/setRating(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic, wound) return getArmor((isnull(melee) ? src.melee : melee),\ (isnull(bullet) ? src.bullet : bullet),\ (isnull(laser) ? src.laser : laser),\ @@ -47,19 +49,20 @@ (isnull(rad) ? src.rad : rad),\ (isnull(fire) ? src.fire : fire),\ (isnull(acid) ? src.acid : acid),\ - (isnull(magic) ? src.magic : magic)) + (isnull(magic) ? src.magic : magic),\ + (isnull(wound) ? src.wound : wound)) /datum/armor/proc/getRating(rating) return vars[rating] /datum/armor/proc/getList() - return list("melee" = melee, "bullet" = bullet, "laser" = laser, "energy" = energy, "bomb" = bomb, "bio" = bio, "rad" = rad, "fire" = fire, "acid" = acid, "magic" = magic) + return list("melee" = melee, "bullet" = bullet, "laser" = laser, "energy" = energy, "bomb" = bomb, "bio" = bio, "rad" = rad, "fire" = fire, "acid" = acid, "magic" = magic, "wound" = wound) /datum/armor/proc/attachArmor(datum/armor/AA) - return getArmor(melee+AA.melee, bullet+AA.bullet, laser+AA.laser, energy+AA.energy, bomb+AA.bomb, bio+AA.bio, rad+AA.rad, fire+AA.fire, acid+AA.acid, magic+AA.magic) + return getArmor(melee+AA.melee, bullet+AA.bullet, laser+AA.laser, energy+AA.energy, bomb+AA.bomb, bio+AA.bio, rad+AA.rad, fire+AA.fire, acid+AA.acid, magic+AA.magic, wound+AA.wound) /datum/armor/proc/detachArmor(datum/armor/AA) - return getArmor(melee-AA.melee, bullet-AA.bullet, laser-AA.laser, energy-AA.energy, bomb-AA.bomb, bio-AA.bio, rad-AA.rad, fire-AA.fire, acid-AA.acid, magic-AA.magic) + return getArmor(melee-AA.melee, bullet-AA.bullet, laser-AA.laser, energy-AA.energy, bomb-AA.bomb, bio-AA.bio, rad-AA.rad, fire-AA.fire, acid-AA.acid, magic-AA.magic, wound-AA.wound) /datum/armor/vv_edit_var(var_name, var_value) if (var_name == NAMEOF(src, tag)) @@ -67,4 +70,4 @@ . = ..() tag = ARMORID // update tag in case armor values were edited -#undef ARMORID +#undef ARMORID \ No newline at end of file diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm index 1af6f91d5885..5242567535ad 100644 --- a/code/datums/brain_damage/magic.dm +++ b/code/datums/brain_damage/magic.dm @@ -94,7 +94,7 @@ if(get_dist(owner, stalker) <= 1) playsound(owner, 'sound/magic/demon_attack1.ogg', 50) owner.visible_message("[owner] is torn apart by invisible claws!", "Ghostly claws tear your body apart!") - owner.take_bodypart_damage(rand(20, 45)) + owner.take_bodypart_damage(rand(20, 45), wound_bonus=CANT_WOUND) else if(prob(50)) stalker.forceMove(get_step_towards(stalker, owner)) if(get_dist(owner, stalker) <= 8) diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index 475b12b820ce..796e05183b99 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -104,13 +104,11 @@ ..() for(var/X in paralysis_traits) ADD_TRAIT(owner, X, "trauma_paralysis") - owner.update_disabled_bodyparts() /datum/brain_trauma/severe/paralysis/on_lose() ..() for(var/X in paralysis_traits) REMOVE_TRAIT(owner, X, "trauma_paralysis") - owner.update_disabled_bodyparts() /datum/brain_trauma/severe/paralysis/paraplegic random_gain = FALSE diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm index d6804564cb78..4ec5d0b44ec9 100644 --- a/code/datums/components/caltrop.dm +++ b/code/datums/components/caltrop.dm @@ -48,7 +48,7 @@ var/damage = rand(min_damage, max_damage) if(HAS_TRAIT(H, TRAIT_LIGHT_STEP)) damage *= 0.75 - H.apply_damage(damage, BRUTE, picked_def_zone) + H.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND) if(cooldown < world.time - 10) //cooldown to avoid message spam. if(!H.incapacitated(ignore_restraints = TRUE)) diff --git a/code/datums/components/fantasy/_fantasy.dm b/code/datums/components/fantasy/_fantasy.dm index 383dd7c10da0..6aee717cdf34 100644 --- a/code/datums/components/fantasy/_fantasy.dm +++ b/code/datums/components/fantasy/_fantasy.dm @@ -92,6 +92,8 @@ master.force = max(0, master.force + quality) master.throwforce = max(0, master.throwforce + quality) master.armor = master.armor.modifyAllRatings(quality) + master.wound_bonus += quality + master.bare_wound_bonus += quality var/newName = originalName for(var/i in affixes) @@ -123,6 +125,8 @@ master.force = max(0, master.force - quality) master.throwforce = max(0, master.throwforce - quality) master.armor = master.armor.modifyAllRatings(-quality) + master.wound_bonus -= quality + master.bare_wound_bonus -= quality master.name = originalName diff --git a/code/datums/components/gunpoint.dm b/code/datums/components/gunpoint.dm index 279d7635f8a8..316aaf21717e 100644 --- a/code/datums/components/gunpoint.dm +++ b/code/datums/components/gunpoint.dm @@ -1,8 +1,16 @@ -#define GUNPOINT_SHOOTER_STRAY_RANGE 3 -#define GUNPOINT_DELAY_STAGE_2 25 -#define GUNPOINT_DELAY_STAGE_3 85 // cumulative with past stages, so 100 deciseconds +/// How many tiles around the target the shooter can roam without losing their shot +#define GUNPOINT_SHOOTER_STRAY_RANGE 2 +/// How long it takes from the gunpoint is initiated to reach stage 2 +#define GUNPOINT_DELAY_STAGE_2 2.5 SECONDS +/// How long it takes from stage 2 starting to move up to stage 3 +#define GUNPOINT_DELAY_STAGE_3 7.5 SECONDS +/// If the projectile doesn't have a wound_bonus of CANT_WOUND, we add (this * the stage mult) to their wound_bonus and bare_wound_bonus upon triggering +#define GUNPOINT_BASE_WOUND_BONUS 5 +/// How much the damage and wound bonus mod is multiplied when you're on stage 1 #define GUNPOINT_MULT_STAGE_1 1 +/// As above, for stage 2 #define GUNPOINT_MULT_STAGE_2 2 +/// As above, for stage 3 #define GUNPOINT_MULT_STAGE_3 2.5 @@ -29,8 +37,8 @@ var/mob/living/shooter = parent target = targ weapon = wep + RegisterSignal(targ, COMSIG_MOVABLE_MOVED, .proc/check_movement, FALSE) //except this one RegisterSignal(targ, list(COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_FIRED_GUN, COMSIG_MOB_THROW, COMSIG_MOB_ITEM_ATTACK), .proc/trigger_reaction, TRUE) //any actions by the hostage will trigger the shot no exceptions - RegisterSignal(targ, COMSIG_MOVABLE_MOVED, .proc/trigger_reaction) //except this one RegisterSignal(weapon, list(COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED), .proc/cancel) shooter.visible_message("[shooter] aims [weapon] point blank at [target]!", \ @@ -64,7 +72,7 @@ UnregisterSignal(parent, COMSIG_HUMAN_DISARM_HIT) UnregisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOB_THROW, COMSIG_MOB_FIRED_GUN, COMSIG_MOB_TABLING)) -// if you're gonna try to break away from a holdup, better to do it right away +///Update the damage multiplier for whatever stage we're entering into /datum/component/gunpoint/proc/update_stage(new_stage) stage = new_stage if(stage == 2) @@ -81,16 +89,15 @@ if(!can_see(parent, target, GUNPOINT_SHOOTER_STRAY_RANGE - 1)) cancel() -/datum/component/gunpoint/proc/trigger_reaction(var/forced) +/datum/component/gunpoint/proc/trigger_reaction() var/mob/living/shooter = parent - - if(!forced && shooter.pulling == target) //target won't get shot if they're being moved by the shooter - return if(disrupted) return if(point_of_no_return) return point_of_no_return = TRUE + shooter.remove_status_effect(STATUS_EFFECT_HOLDUP) // try doing these before the trigger gets pulled since the target (or shooter even) may not exist after pulling the trigger, dig? + target.remove_status_effect(STATUS_EFFECT_HELDUP) if(!weapon.can_shoot() || !weapon.can_trigger_gun(shooter) || (weapon.weapon_weight == WEAPON_HEAVY && shooter.get_inactive_held_item())) shooter.visible_message("[shooter] fumbles [weapon]!", \ @@ -103,10 +110,19 @@ if(weapon.chambered && weapon.chambered.BB) weapon.chambered.BB.damage *= damage_mult weapon.chambered.BB.stamina *= damage_mult - - weapon.process_fire(target, shooter) + if(weapon.chambered.BB.wound_bonus != CANT_WOUND) + weapon.chambered.BB.wound_bonus += damage_mult * GUNPOINT_BASE_WOUND_BONUS + weapon.chambered.BB.bare_wound_bonus += damage_mult * GUNPOINT_BASE_WOUND_BONUS + + var/fired = weapon.process_fire(target, shooter) + if(!fired && weapon.chambered?.BB) + weapon.chambered.BB.damage /= damage_mult + if(weapon.chambered.BB.wound_bonus != CANT_WOUND) + weapon.chambered.BB.wound_bonus -= damage_mult * GUNPOINT_BASE_WOUND_BONUS + weapon.chambered.BB.bare_wound_bonus -= damage_mult * GUNPOINT_BASE_WOUND_BONUS qdel(src) +///called if the shooter does anything that would cause the target to move, preventing a charged shot from being fired for a short duration /datum/component/gunpoint/proc/noshooted() if(!disrupted) disrupted = TRUE @@ -123,8 +139,16 @@ to_chat(target, "[shooter] breaks [shooter.p_their()] aim on you!") qdel(src) -/datum/component/gunpoint/proc/flinch(damage, damagetype, def_zone) +/datum/component/gunpoint/proc/check_movement() var/mob/living/shooter = parent + if(!(shooter.pulling == target)) //target won't get shot if they're being moved by the shooter + trigger_reaction() + +///If the shooter is hit by an attack, they have a 50% chance to flinch and fire. If it hit the arm holding the trigger, it's an 80% chance to fire instead +/datum/component/gunpoint/proc/flinch(attacker, damage, damagetype, def_zone) + var/mob/living/shooter = parent + if(attacker == shooter) + return // somehow this wasn't checked for months but no one tried punching themselves to initiate the shot, amazing var/flinch_chance = 50 var/gun_hand = LEFT_HANDS @@ -138,7 +162,7 @@ if(prob(flinch_chance)) shooter.visible_message("[shooter] flinches!", \ "You flinch!") - trigger_reaction(TRUE) //flinching will always result in firing at the target + trigger_reaction() //flinching will always result in firing at the target /datum/component/gunpoint/proc/flinch_disarm(attacker,zone_targeted) var/mob/living/shooter = parent @@ -160,6 +184,7 @@ #undef GUNPOINT_SHOOTER_STRAY_RANGE #undef GUNPOINT_DELAY_STAGE_2 #undef GUNPOINT_DELAY_STAGE_3 +#undef GUNPOINT_BASE_WOUND_BONUS #undef GUNPOINT_MULT_STAGE_1 #undef GUNPOINT_MULT_STAGE_2 #undef GUNPOINT_MULT_STAGE_3 diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm index 8ea36096a58d..5c2c7631935f 100644 --- a/code/datums/components/riding.dm +++ b/code/datums/components/riding.dm @@ -339,6 +339,9 @@ else inhand.rider = riding_target_override inhand.parent = AM + for(var/obj/item/I in user.held_items) // delete any hand items like slappers that could still totally be used to grab on + if((I.obj_flags & HAND_ITEM)) + qdel(I) if(user.put_in_hands(inhand, TRUE)) amount_equipped++ else diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm index b180b72f4e7f..13abac03981f 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -61,7 +61,8 @@ Bonus if(bleed) if(ishuman(M)) var/mob/living/carbon/human/H = M - H.bleed_rate += 5 * power + var/obj/item/bodypart/random_part = pick(H.bodyparts) + random_part.generic_bleedstacks += 5 * power return 1 /* diff --git a/code/datums/martial/flying_fang.dm b/code/datums/martial/flying_fang.dm index 257e75b0fa6d..60b36fcb6f91 100644 --- a/code/datums/martial/flying_fang.dm +++ b/code/datums/martial/flying_fang.dm @@ -92,7 +92,7 @@ A.do_attack_animation(D, ATTACK_EFFECT_BITE) playsound(D, 'sound/weapons/bite.ogg', 50, TRUE, -1) D.apply_damage(30, A.dna.species.attack_type, BODY_ZONE_HEAD, armor_block) - D.bleed_rate += 10 + //D.bleed_rate += 10 D.visible_message("[A] takes a large bite out of [D]'s neck!", \ "[A] takes a large bite out of your neck!") if(D.health > 0) @@ -240,7 +240,7 @@ to_chat(usr, "Your training has rendered you more resistant to pain, allowing you to keep fighting effectively for longer and reducing the effectiveness of stun and stamina weapons by about a third.") to_chat(usr, "However, the primitive instincts gained through this training prevent you from using guns, stun weapons, or armor.") to_chat(usr, "All of your unarmed attacks deal increased brute damage with a small amount of armor piercing") - + to_chat(usr, "Disarm Intent: Headbutt your enemy, Deals minor stamina and brute damage, as well as causing eye blurriness. Prevents the target from using ranged weapons effectively for a few seconds if they are not wearing a helmet.") to_chat(usr, "Tail Slap: Disarm Disarm Disarm. High armor piercing attack that causes a short slow followed by a knockdown. Deals heavy stamina damage.") diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm index 69a0352a1688..e14c2f915d67 100644 --- a/code/datums/martial/sleeping_carp.dm +++ b/code/datums/martial/sleeping_carp.dm @@ -46,7 +46,7 @@ playsound(get_turf(A), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) D.emote("scream") D.dropItemToGround(D.get_active_held_item()) - D.apply_damage(5, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + D.apply_damage(5, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM), wound_bonus = CANT_WOUND) D.Stun(60) return TRUE @@ -88,7 +88,7 @@ A.do_attack_animation(D, ATTACK_EFFECT_KICK) D.visible_message("[A] kicks [D] in the head!", \ "[A] kicks you in the jaw!") - D.apply_damage(20, A.dna.species.attack_type, BODY_ZONE_HEAD) + D.apply_damage(20, A.dna.species.attack_type, BODY_ZONE_HEAD, wound_bonus = CANT_WOUND) D.drop_all_held_items() playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1) D.Stun(80) @@ -103,7 +103,7 @@ "[A] piledrives you with their elbow!") if(D.stat) D.death() //FINISH HIM! - D.apply_damage(50, A.dna.species.attack_type, BODY_ZONE_CHEST) + D.apply_damage(50, A.dna.species.attack_type, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND) playsound(get_turf(D), 'sound/weapons/punch1.ogg', 75, 1, -1) return TRUE return basic_hit(A,D) @@ -133,7 +133,7 @@ var/atk_verb = pick("punches", "kicks", "chops", "hits", "slams") D.visible_message("[A] [atk_verb] [D]!", \ "[A] [atk_verb] you!") - D.apply_damage(rand(10,15), BRUTE) + D.apply_damage(rand(10,15), BRUTE, wound_bonus = CANT_WOUND) playsound(get_turf(D), 'sound/weapons/punch1.ogg', 25, 1, -1) if(prob(D.getBruteLoss()) && (D.mobility_flags & MOBILITY_STAND)) D.visible_message("[D] stumbles and falls!", "The blow sends you to the ground!") diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 15543bbcb108..e562f275861f 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -73,6 +73,14 @@ var/flavour_text = null ///Are we zombified/uncloneable? var/zombified = FALSE + ///What character we joined in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not + var/mob/original_character + /// What scar slot we have loaded, so we don't have to constantly check the savefile + var/current_scar_slot + /// The index for what character slot, if any, we were loaded from, so we can track persistent scars on a per-character basis. Each character slot gets PERSISTENT_SCAR_SLOTS scar slots + var/original_character_slot_index + /// The index for our current scar slot, so we don't have to constantly check the savefile (unlike the slots themselves, this index is independent of selected char slot, and increments whenever a valid char is joined with) + var/current_scar_slot_index /datum/mind/New(key) src.key = key @@ -94,6 +102,7 @@ return language_holder /datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0) + original_character = null var/mood_was_enabled = FALSE//Yogs -- Mood Preferences if(current) // remove ourself from our old body's mind variable // Yogs start -- Mood preferences @@ -148,6 +157,7 @@ new_character.key = key //now transfer the key to link the client to our new body if(new_character.client) new_character.client.init_verbs() // re-initialize character specific verbs + LAZYCLEARLIST(new_character.client.recent_examines) current.update_atom_languages() /datum/mind/proc/set_death_time() diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 460bdff3cd82..954733ca3a0b 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -348,7 +348,7 @@ var/mob/living/carbon/human/H = owner prot = H.get_thermal_protection() - + if(owner.on_fire && (prot < FIRE_IMMUNITY_MAX_TEMP_PROTECT)) linked_alert.icon_state = "fleshmend_fire" return @@ -357,6 +357,10 @@ owner.adjustBruteLoss(-10, FALSE) owner.adjustFireLoss(-5, FALSE) owner.adjustOxyLoss(-10) + if(!iscarbon(owner)) + return + var/mob/living/carbon/C = owner + QDEL_LIST(C.all_scars) /obj/screen/alert/status_effect/fleshmend name = "Fleshmend" diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index 1d68f53f48ec..e3cd00f7765a 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -885,10 +885,11 @@ effect_sprite = "emark1" /datum/status_effect/eldritch/flesh/on_effect() - if(ishuman(owner)) var/mob/living/carbon/human/H = owner - H.bleed_rate += 10 + var/obj/item/bodypart/bodypart = pick(H.bodyparts) + var/datum/wound/slash/severe/crit_wound = new + crit_wound.apply_wound(bodypart) return ..() /datum/status_effect/eldritch/ash diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index d6377c115e75..a23cf4390fdd 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -70,6 +70,10 @@ return duration = world.time + original_duration +//do_after modifier! +/datum/status_effect/proc/interact_speed_modifier() + return 1 + //clickdelay/nextmove modifiers! /datum/status_effect/proc/nextmove_modifier() return 1 diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm new file mode 100644 index 000000000000..d9cc97fcebd7 --- /dev/null +++ b/code/datums/status_effects/wound_effects.dm @@ -0,0 +1,196 @@ + +// The shattered remnants of your broken limbs fill you with determination! +/obj/screen/alert/status_effect/determined + name = "Determined" + desc = "The serious wounds you've sustained have put your body into fight-or-flight mode! Now's the time to look for an exit!" + icon_state = "regenerative_core" + +/datum/status_effect/determined + id = "determined" + alert_type = /obj/screen/alert/status_effect/determined + +/datum/status_effect/determined/on_apply() + . = ..() + owner.visible_message("[owner]'s body tenses up noticeably, gritting against [owner.p_their()] pain!", "Your senses sharpen as your body tenses up from the wounds you've sustained!", \ + vision_distance=COMBAT_MESSAGE_RANGE) + if(ishuman(owner)) + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.bleed_mod *= WOUND_DETERMINATION_BLEED_MOD + +/datum/status_effect/determined/on_remove() + owner.visible_message("[owner]'s body slackens noticeably!", "Your adrenaline rush dies off, and the pain from your wounds come aching back in...", vision_distance=COMBAT_MESSAGE_RANGE) + if(ishuman(owner)) + var/mob/living/carbon/human/human_owner = owner + human_owner.physiology.bleed_mod /= WOUND_DETERMINATION_BLEED_MOD + return ..() + +/datum/status_effect/limp + id = "limp" + status_type = STATUS_EFFECT_REPLACE + tick_interval = 10 + alert_type = /obj/screen/alert/status_effect/limp + var/msg_stage = 0//so you dont get the most intense messages immediately + /// The left leg of the limping person + var/obj/item/bodypart/l_leg/left + /// The right leg of the limping person + var/obj/item/bodypart/r_leg/right + /// Which leg we're limping with next + var/obj/item/bodypart/next_leg + /// How many deciseconds we limp for on the left leg + var/slowdown_left = 0 + /// How many deciseconds we limp for on the right leg + var/slowdown_right = 0 + +/datum/status_effect/limp/on_apply() + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/C = owner + left = C.get_bodypart(BODY_ZONE_L_LEG) + right = C.get_bodypart(BODY_ZONE_R_LEG) + update_limp() + RegisterSignal(C, COMSIG_MOVABLE_MOVED, .proc/check_step) + RegisterSignal(C, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB), .proc/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)) + +/obj/screen/alert/status_effect/limp + name = "Limping" + desc = "One or more of your legs has been wounded, slowing down steps with that leg! Get it fixed, or at least in a sling of gauze!" + +/datum/status_effect/limp/proc/check_step(mob/whocares, OldLoc, Dir, forced) + if(!owner.client || !(owner.mobility_flags & MOBILITY_STAND) || !owner.has_gravity() || (owner.movement_type & FLYING) || forced || owner.buckled) + return + // less limping while we have determination still + var/determined_mod = owner.has_status_effect(STATUS_EFFECT_DETERMINED) ? 0.25 : 1 + + if(next_leg == left) + owner.client.move_delay += slowdown_left * determined_mod + next_leg = right + else + owner.client.move_delay += slowdown_right * determined_mod + next_leg = left + +/datum/status_effect/limp/proc/update_limp() + var/mob/living/carbon/C = owner + left = C.get_bodypart(BODY_ZONE_L_LEG) + right = C.get_bodypart(BODY_ZONE_R_LEG) + + if(!left && !right) + C.remove_status_effect(src) + return + + slowdown_left = 0 + slowdown_right = 0 + + if(left) + for(var/thing in left.wounds) + var/datum/wound/W = thing + slowdown_left += W.limp_slowdown + + if(right) + for(var/thing in right.wounds) + var/datum/wound/W = thing + slowdown_right += W.limp_slowdown + + // this handles losing your leg with the limp and the other one being in good shape as well + if(!slowdown_left && !slowdown_right) + C.remove_status_effect(src) + return + + +///////////////////////// +//////// WOUNDS ///////// +///////////////////////// + +// wound alert +/obj/screen/alert/status_effect/wound + name = "Wounded" + desc = "Your body has sustained serious damage, click here to inspect yourself." + +/obj/screen/alert/status_effect/wound/Click() + var/mob/living/carbon/human/H = usr + H.check_self_for_injuries() + +// wound status effect base +/datum/status_effect/wound + id = "wound" + status_type = STATUS_EFFECT_MULTIPLE + var/obj/item/bodypart/linked_limb + var/datum/wound/linked_wound + alert_type = NONE + +/datum/status_effect/wound/on_creation(mob/living/new_owner, incoming_wound) + . = ..() + linked_wound = incoming_wound + linked_limb = linked_wound.limb + +/datum/status_effect/wound/on_remove() + linked_wound = null + linked_limb = null + UnregisterSignal(owner, COMSIG_CARBON_LOSE_WOUND) + +/datum/status_effect/wound/on_apply() + if(!iscarbon(owner)) + return FALSE + RegisterSignal(owner, COMSIG_CARBON_LOSE_WOUND, .proc/check_remove) + return TRUE + +/// check if the wound getting removed is the wound we're tied to +/datum/status_effect/wound/proc/check_remove(mob/living/L, datum/wound/W) + if(W == linked_wound) + qdel(src) + + +// bones +/datum/status_effect/wound/blunt + +/datum/status_effect/wound/blunt/interact_speed_modifier() + var/mob/living/carbon/C = owner + + if(C.get_active_hand() == linked_limb) + to_chat(C, "The [lowertext(linked_wound)] in your [linked_limb.name] slows your progress!") + return linked_wound.interaction_efficiency_penalty + + return 1 + +/datum/status_effect/wound/blunt/nextmove_modifier() + var/mob/living/carbon/C = owner + + if(C.get_active_hand() == linked_limb) + return linked_wound.interaction_efficiency_penalty + + return 1 + +// blunt +/datum/status_effect/wound/blunt/moderate + id = "disjoint" +/datum/status_effect/wound/blunt/severe + id = "hairline" +/datum/status_effect/wound/blunt/critical + id = "compound" + +// slash +/datum/status_effect/wound/slash/moderate + id = "abrasion" +/datum/status_effect/wound/slash/severe + id = "laceration" +/datum/status_effect/wound/slash/critical + id = "avulsion" + +// pierce +/datum/status_effect/wound/pierce/moderate + id = "breakage" +/datum/status_effect/wound/pierce/severe + id = "puncture" +/datum/status_effect/wound/pierce/critical + id = "rupture" + +// burns +/datum/status_effect/wound/burn/moderate + id = "seconddeg" +/datum/status_effect/wound/burn/severe + id = "thirddeg" +/datum/status_effect/wound/burn/critical + id = "fourthdeg" diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm new file mode 100644 index 000000000000..281d694135e2 --- /dev/null +++ b/code/datums/wounds/_wounds.dm @@ -0,0 +1,376 @@ +/* + Wounds are specific medical complications that can arise and be applied to (currently) carbons, with a focus on humans. All of the code for and related to this is heavily WIP, + and the documentation will be slanted towards explaining what each part/piece is leading up to, until such a time as I finish the core implementations. The original design doc + can be found at https://hackmd.io/@Ryll/r1lb4SOwU + + Wounds are datums that operate like a mix of diseases, brain traumas, and components, and are applied to a /obj/item/bodypart (preferably attached to a carbon) when they take large spikes of damage + or under other certain conditions (thrown hard against a wall, sustained exposure to plasma fire, etc). Wounds are categorized by the three following criteria: + 1. Severity: Either MODERATE, SEVERE, or CRITICAL. See the hackmd for more details + 2. Viable zones: What body parts the wound is applicable to. Generic wounds like broken bones and severe burns can apply to every zone, but you may want to add special wounds for certain limbs + like a twisted ankle for legs only, or open air exposure of the organs for particularly gruesome chest wounds. Wounds should be able to function for every zone they are marked viable for. + 3. Damage type: Currently either BRUTE or BURN. Again, see the hackmd for a breakdown of my plans for each type. + + When a body part suffers enough damage to get a wound, the severity (determined by a roll or something, worse damage leading to worse wounds), affected limb, and damage type sustained are factored into + deciding what specific wound will be applied. I'd like to have a few different types of wounds for at least some of the choices, but I'm just doing rough generals for now. Expect polishing +*/ + +/datum/wound + /// What it's named + var/name = "Wound" + /// The description shown on the scanners + var/desc = "" + /// The basic treatment suggested by health analyzers + var/treat_text = "" + /// What the limb looks like on a cursory examine + var/examine_desc = "is badly hurt" + + /// needed for "your arm has a compound fracture" vs "your arm has some third degree burns" + var/a_or_from = "a" + /// The visible message when this happens + var/occur_text = "" + /// This sound will be played upon the wound being applied + var/sound_effect + + /// 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 list of wounds it belongs in, WOUND_LIST_BLUNT, WOUND_LIST_SLASH, or WOUND_LIST_BURN + var/wound_type + + /// What body zones can we affect + 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) + /// Who owns the body part that we're wounding + var/mob/living/carbon/victim = null + /// The bodypart we're parented to + var/obj/item/bodypart/limb = null + + /// Specific items such as bandages or sutures that can try directly treating this wound + 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 + /// 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 + + /// Using this limb in a do_after interaction will multiply the length by this duration (arms) + var/interaction_efficiency_penalty = 1 + /// Incoming damage on this limb will be multiplied by this, to simulate tenderness and vulnerability (mostly burns). + var/damage_mulitplier_penalty = 1 + /// If set and this wound is applied to a leg, we take this many deciseconds extra per step on this leg + var/limp_slowdown + /// How much we're contributing to this limb's bleed_rate + var/blood_flow + + /// 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 + /// If we need to process each life tick + var/processes = FALSE + + /// If having this wound makes currently makes the parent bodypart unusable + var/disabling + + /// What status effect we assign on application + var/status_effect_type + /// The status effect we're linked to + var/datum/status_effect/linked_status_effect + /// 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 + var/cryo_progress + + /// What kind of scars this wound will create description wise once healed + var/scar_keyword = "generic" + /// If we've already tried scarring while removing (since remove_wound calls qdel, and qdel calls remove wound, .....) TODO: make this cleaner + var/already_scarred = FALSE + /// If we forced this wound through badmin smite, we won't count it towards the round totals + var/from_smite + + /// What flags apply to this wound + var/wound_flags = (FLESH_WOUND | BONE_WOUND | ACCEPTS_GAUZE) + +/datum/wound/Destroy() + if(attached_surgery) + QDEL_NULL(attached_surgery) + if(limb?.wounds && (src in limb.wounds)) // destroy can call remove_wound() and remove_wound() calls qdel, so we check to make sure there's anything to remove first + remove_wound() + set_limb(null) + victim = null + return ..() + +/** + * apply_wound() is used once a wound type is instantiated to assign it to a bodypart, and actually come into play. + * + * + * Arguments: + * * L: The bodypart we're wounding, we don't care about the person, we can get them through the limb + * * silent: Not actually necessary I don't think, was originally used for demoting wounds so they wouldn't make new messages, but I believe old_wound took over that, I may remove this shortly + * * old_wound: If our new wound is a replacement for one of the same time (promotion or demotion), we can reference the old one just before it's removed to copy over necessary vars + * * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented) + */ +/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE) + if(!istype(L) || !L.owner || !(L.body_zone in viable_zones) || isalien(L.owner) || !L.is_organic_limb()) + qdel(src) + return + + if(ishuman(L.owner)) + var/mob/living/carbon/human/H = L.owner + if(((wound_flags & BONE_WOUND) && !(HAS_BONE in H.dna.species.species_traits)) || ((wound_flags & FLESH_WOUND) && !(HAS_FLESH in H.dna.species.species_traits))) + qdel(src) + return + + // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check + // in case we ever directly add wounds + for(var/i in L.wounds) + var/datum/wound/preexisting_wound = i + if((preexisting_wound.type == type) && (preexisting_wound != old_wound)) + qdel(src) + return + + victim = L.owner + set_limb(L) + LAZYADD(victim.all_wounds, src) + LAZYADD(limb.wounds, src) + limb.update_wounds() + if(status_effect_type) + linked_status_effect = victim.apply_status_effect(status_effect_type, src) + SEND_SIGNAL(victim, COMSIG_CARBON_GAIN_WOUND, src, limb) + if(!victim.alerts["wound"]) // only one alert is shared between all of the wounds + victim.throw_alert("wound", /obj/screen/alert/status_effect/wound) + + var/demoted + if(old_wound) + demoted = (severity <= old_wound.severity) + + if(severity == WOUND_SEVERITY_TRIVIAL) + return + + if(!(silent || demoted)) + var/msg = "[victim]'s [limb.name] [occur_text]!" + var/vis_dist = COMBAT_MESSAGE_RANGE + + if(severity != WOUND_SEVERITY_MODERATE) + msg = "[msg]" + vis_dist = DEFAULT_MESSAGE_RANGE + + victim.visible_message(msg, "Your [limb.name] [occur_text]!", vision_distance = vis_dist) + if(sound_effect) + playsound(L.owner, sound_effect, 70 + 20 * severity, TRUE) + + if(!demoted) + wound_injury(old_wound) + second_wind() + +/// Remove the wound from whatever it's afflicting, and cleans up whateverstatus effects it had or modifiers it had on interaction times. ignore_limb is used for detachments where we only want to forget the victim +/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 + set_disabling(FALSE) + if(limb && !already_scarred && !replaced) + already_scarred = TRUE + var/datum/scar/new_scar = new + new_scar.generate(limb, src) + if(victim) + LAZYREMOVE(victim.all_wounds, src) + if(!victim.all_wounds) + victim.clear_alert("wound") + SEND_SIGNAL(victim, COMSIG_CARBON_LOSE_WOUND, src, limb) + if(limb && !ignore_limb) + LAZYREMOVE(limb.wounds, src) + limb.update_wounds(replaced) + if(limb?.can_be_disabled) + limb.update_disabled() + +/** + * replace_wound() is used when you want to replace the current wound with a new wound, presumably of the same category, just of a different severity (either up or down counts) + * + * This proc actually instantiates the new wound based off the specific type path passed, then returns the new instantiated wound datum. + * + * Arguments: + * * new_type- The TYPE PATH of the wound you want to replace this, like /datum/wound/slash/severe + * * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented) + */ +/datum/wound/proc/replace_wound(new_type, smited = FALSE) + var/datum/wound/new_wound = new new_type + already_scarred = TRUE + remove_wound(replaced=TRUE) + new_wound.apply_wound(limb, old_wound = src, smited = smited) + . = new_wound + qdel(src) + +/// The immediate negative effects faced as a result of the wound +/datum/wound/proc/wound_injury(datum/wound/old_wound = null) + return + +/// Proc called to change the variable `limb` and react to the event. +/datum/wound/proc/set_limb(new_value) + 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 + limb = new_value + if(. && disabling) + var/obj/item/bodypart/old_limb = . + REMOVE_TRAIT(old_limb, TRAIT_PARALYSIS, src) + REMOVE_TRAIT(old_limb, TRAIT_DISABLED_BY_WOUND, src) + if(limb) + if(disabling) + ADD_TRAIT(limb, TRAIT_PARALYSIS, src) + ADD_TRAIT(limb, TRAIT_DISABLED_BY_WOUND, src) + + +/// Proc called to change the variable `disabling` and react to the event. +/datum/wound/proc/set_disabling(new_value) + if(disabling == new_value) + return + . = disabling + disabling = new_value + if(disabling) + if(!. && limb) //Gained disabling. + ADD_TRAIT(limb, TRAIT_PARALYSIS, src) + ADD_TRAIT(limb, TRAIT_DISABLED_BY_WOUND, src) + else if(. && limb) //Lost disabling. + REMOVE_TRAIT(limb, TRAIT_PARALYSIS, src) + REMOVE_TRAIT(limb, TRAIT_DISABLED_BY_WOUND, src) + +/// 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() + switch(severity) + if(WOUND_SEVERITY_MODERATE) + victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_MODERATE) + if(WOUND_SEVERITY_SEVERE) + victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_SEVERE) + if(WOUND_SEVERITY_CRITICAL) + victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_CRITICAL) + if(WOUND_SEVERITY_LOSS) + victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_LOSS) + +/** + * try_treating() is an intercept run from [/mob/living/carbon/proc/attackby] right after surgeries but before anything else. Return TRUE here if the item is something that is relevant to treatment to take over the interaction. + * + * This proc leads into [/datum/wound/proc/treat] and probably shouldn't be added onto in children types. You can specify what items or tools you want to be intercepted + * with var/list/treatable_by and var/treatable_tool, then if an item fulfills one of those requirements and our wound claims it first, it goes over to treat() and treat_self(). + * + * Arguments: + * * I: The item we're trying to use + * * user: The mob trying to use it on us + */ +/datum/wound/proc/try_treating(obj/item/I, mob/user) + // first we weed out if we're not dealing with our wound's bodypart, or if it might be an attack + if(QDELETED(I) || limb.body_zone != user.zone_selected || (I.force && user.a_intent != INTENT_HELP)) + return FALSE + + var/allowed = FALSE + + // check if we have a valid treatable tool + if(I.tool_behaviour == treatable_tool) + allowed = TRUE + else if((treatable_tool == TOOL_CAUTERY && I.get_temperature() || istype(I, /obj/item/gun/energy/laser)) && user == victim) // allow improvised cauterization on yourself without an aggro grab allowed = TRUE + allowed = TRUE + // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only + else if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(I, user)) + allowed = TRUE + // failing THAT, we check if we have a generally allowed item + else + for(var/allowed_type in treatable_by) + if(istype(I, allowed_type)) + allowed = TRUE + break + + // if none of those apply, we return false to avoid interrupting + if(!allowed) + return FALSE + + // now that we've determined we have a valid attempt at treating, we can stomp on their dreams if we're already interacting with the patient + if(INTERACTING_WITH(user, victim)) + to_chat(user, "You're already interacting with [victim]!") + return TRUE + + if(!victim.can_inject(user, TRUE)) + return TRUE + + // lastly, treat them + treat(I, user) + return TRUE + +/// Return TRUE if we have an item that can only be used while aggro grabbed (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling]). Treatment is still is handled in [/datum/wound/proc/treat] +/datum/wound/proc/check_grab_treatments(obj/item/I, mob/user) + return FALSE + +/// Like try_treating() but for unhanded interactions from humans, used by joint dislocations for manual bodypart chiropractice for example. Ignores thick material checks since you can pop an arm into place through a thick suit unlike using sutures +/datum/wound/proc/try_handling(mob/living/carbon/human/user) + return FALSE + +/// Someone is using something that might be used for treating the wound on this limb +/datum/wound/proc/treat(obj/item/I, mob/user) + return + +/// If var/processing is TRUE, this is run on each life tick +/datum/wound/proc/handle_process() + return + +/// For use in do_after callback checks +/datum/wound/proc/still_exists() + return (!QDELETED(src) && limb) + +/// When our parent bodypart is hurt +/datum/wound/proc/receive_damage(wounding_type, wounding_dmg, wound_bonus) + return + +/// Called from cryoxadone and pyroxadone when they're proc'ing. Wounds will slowly be fixed separately from other methods when these are in effect. crappy name but eh +/datum/wound/proc/on_xadone(power) + cryo_progress += power + if(cryo_progress > 33 * severity) + qdel(src) + +/// 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 + +/// Called when the patient is undergoing stasis, so that having fully treated a wound doesn't make you sit there helplessly until you think to unbuckle them +/datum/wound/proc/on_stasis() + return + +/// Called when we're crushed in an airlock or firedoor, for one of the improvised joint dislocation fixes +/datum/wound/proc/crush() + return + +/// Used when we're being dragged while bleeding, the value we return is how much bloodloss this wound causes from being dragged. Since it's a proc, you can let bandages soak some of the blood +/datum/wound/proc/drag_bleed_amount() + return + + +/** + * get_bleed_rate_of_change() is used in [/mob/living/carbon/proc/bleed_warn] to gauge whether this wound (if bleeding) is becoming worse, better, or staying the same over time + * + * Returns BLOOD_FLOW_STEADY if we're not bleeding or there's no change (like piercing), BLOOD_FLOW_DECREASING if we're clotting (non-critical slashes, gauzed, coagulant, etc), BLOOD_FLOW_INCREASING if we're opening up (crit slashes/heparin) + */ +/datum/wound/proc/get_bleed_rate_of_change() + if(blood_flow && HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) + return BLOOD_FLOW_INCREASING + return BLOOD_FLOW_STEADY + +/** + * get_examine_description() is used in carbon/examine and human/examine to show the status of this wound. Useful if you need to show some status like the wound being splinted or bandaged. + * + * Return the full string line you want to show, note that we're already dealing with the 'warning' span at this point, and that \n is already appended for you in the place this is called from + * + * Arguments: + * * mob/user: The user examining the wound's owner, if that matters + */ +/datum/wound/proc/get_examine_description(mob/user) + . = "[victim.p_their(TRUE)] [limb.name] [examine_desc]" + . = severity <= WOUND_SEVERITY_MODERATE ? "[.]." : "[.]!" + +/datum/wound/proc/get_scanner_description(mob/user) + return "Type: [name]\nSeverity: [severity_text()]\nDescription: [desc]\nRecommended Treatment: [treat_text]" + +/datum/wound/proc/severity_text() + switch(severity) + if(WOUND_SEVERITY_TRIVIAL) + return "Trivial" + if(WOUND_SEVERITY_MODERATE) + return "Moderate" + if(WOUND_SEVERITY_SEVERE) + return "Severe" + if(WOUND_SEVERITY_CRITICAL) + return "Critical" diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm new file mode 100644 index 000000000000..84c68e0576f0 --- /dev/null +++ b/code/datums/wounds/bones.dm @@ -0,0 +1,437 @@ + +/* + Blunt/Bone wounds +*/ +// TODO: well, a lot really, but i'd kill to get overlays and a bonebreaking effect like Blitz: The League, similar to electric shock skeletons + +/* + Base definition +*/ +/datum/wound/blunt + name = "Blunt (Bone) Wound" + sound_effect = 'sound/effects/wounds/crack1.ogg' + wound_type = WOUND_BLUNT + wound_flags = (BONE_WOUND | ACCEPTS_GAUZE) + + /// Have we been taped? + var/taped + /// Have we been bone gel'd? + var/gelled + /// If we did the gel + surgical tape healing method for fractures, how many ticks does it take to heal by default + var/regen_ticks_needed + /// Our current counter for gel + surgical tape regeneration + var/regen_ticks_current + /// If we suffer severe head booboos, we can get brain traumas tied to them + var/datum/brain_trauma/active_trauma + /// What brain trauma group, if any, we can draw from for head wounds + var/brain_trauma_group + /// If we deal brain traumas, when is the next one due? + var/next_trauma_cycle + /// How long do we wait +/- 20% for the next trauma? + var/trauma_cycle_cooldown + /// 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 + +/* + Overwriting of base procs +*/ +/datum/wound/blunt/wound_injury(datum/wound/old_wound = null) + // hook into gaining/losing gauze so crit bone wounds can re-enable/disable depending if they're slung or not + RegisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), .proc/update_inefficiencies) + + if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group) + processes = TRUE + active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND) + next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown) + + RegisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, .proc/attack_with_hurt_hand) + if(limb.held_index && victim.get_item_for_held_index(limb.held_index) && (disabling || prob(30 * severity))) + var/obj/item/I = victim.get_item_for_held_index(limb.held_index) + if(istype(I, /obj/item/twohanded/offhand)) + I = victim.get_inactive_held_item() + + if(I && victim.dropItemToGround(I)) + victim.visible_message("[victim] drops [I] in shock!", "The force on your [limb.name] causes you to drop [I]!", vision_distance=COMBAT_MESSAGE_RANGE) + + update_inefficiencies() + +/datum/wound/blunt/remove_wound(ignore_limb, replaced) + limp_slowdown = 0 + QDEL_NULL(active_trauma) + if(limb) + UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED)) + if(victim) + UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK) + return ..() + +/datum/wound/blunt/handle_process() + . = ..() + if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group && world.time > next_trauma_cycle) + if(active_trauma) + QDEL_NULL(active_trauma) + else + active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND) + next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown) + + if(!gelled) + return + + regen_ticks_current++ + if(victim.mobility_flags & MOBILITY_STAND) + if(prob(50)) + regen_ticks_current += 0.5 + if(victim.IsSleeping() && prob(50)) + regen_ticks_current += 0.5 + + if(prob(severity * 3)) + victim.take_bodypart_damage(rand(1, severity * 2), stamina=rand(2, severity * 2.5), wound_bonus=CANT_WOUND) + if(prob(33)) + to_chat(victim, "You feel a sharp pain in your body as your bones are reforming!") + + if(regen_ticks_current > regen_ticks_needed) + if(!victim || !limb) + qdel(src) + return + to_chat(victim, "Your [limb.name] has recovered from your fracture!") + remove_wound() + +/// If we're a human who's punching something with a broken arm, we might hurt ourselves doing so +/datum/wound/blunt/proc/attack_with_hurt_hand(mob/M, atom/target, proximity) + if(victim.get_active_hand() != limb || victim.a_intent == INTENT_HELP || !ismob(target) || severity <= WOUND_SEVERITY_MODERATE) + return + + // With a severe or critical wound, you have a 15% or 30% chance to proc pain on hit + if(prob((severity - 1) * 15)) + // And you have a 70% or 50% chance to actually land the blow, respectively + if(prob(70 - 20 * (severity - 1))) + to_chat(victim, "The fracture in your [limb.name] shoots with pain as you strike [target]!") + limb.receive_damage(brute=rand(1,5)) + else + victim.visible_message("[victim] weakly strikes [target] with [victim.p_their()] broken [limb.name], recoiling from pain!", \ + "You fail to strike [target] as the fracture in your [limb.name] lights up in unbearable pain!", vision_distance=COMBAT_MESSAGE_RANGE) + victim.emote("scream") + victim.Stun(0.5 SECONDS) + limb.receive_damage(brute=rand(3,7)) + return COMPONENT_NO_ATTACK_HAND + +/datum/wound/blunt/receive_damage(wounding_type, wounding_dmg, wound_bonus) + if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE) + return + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + if(NOBLOOD in human_victim.dna?.species.species_traits) + return + + if(victim.stat != DEAD && limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg)) + var/blood_bled = rand(1, wounding_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5)) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound + switch(blood_bled) + if(1 to 6) + victim.bleed(blood_bled, TRUE) + if(7 to 13) + victim.visible_message("[victim] coughs up a bit of blood from the blow to [victim.p_their()] chest.", "You cough up a bit of blood from the blow to your chest.", vision_distance=COMBAT_MESSAGE_RANGE) + victim.bleed(blood_bled, TRUE) + if(14 to 19) + victim.visible_message("[victim] spits out a string of blood from the blow to [victim.p_their()] chest!", "You spit out a string of blood from the blow to your chest!", vision_distance=COMBAT_MESSAGE_RANGE) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.bleed(blood_bled) + if(20 to INFINITY) + victim.visible_message("[victim] chokes up a spray of blood from the blow to [victim.p_their()] chest!", "You choke up on a spray of blood from the blow to your chest!", vision_distance=COMBAT_MESSAGE_RANGE) + victim.bleed(blood_bled) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.add_splatter_floor(get_step(victim.loc, victim.dir)) + + +/datum/wound/blunt/get_examine_description(mob/user) + if(!limb.current_gauze && !gelled && !taped) + return ..() + + var/list/msg = list() + if(!limb.current_gauze) + msg += "[victim.p_their(TRUE)] [limb.name] [examine_desc]" + else + var/sling_condition = "" + // how much life we have left in these bandages + switch(limb.current_gauze.absorption_capacity) + if(0 to 1.25) + sling_condition = "just barely" + if(1.25 to 2.75) + sling_condition = "loosely" + if(2.75 to 4) + sling_condition = "mostly" + if(4 to INFINITY) + sling_condition = "tightly" + + msg += "[victim.p_their(TRUE)] [limb.name] is [sling_condition] fastened in a sling of [limb.current_gauze.name]" + + if(taped) + msg += ", and appears to be reforming itself under some surgical tape!" + else if(gelled) + msg += ", with fizzing flecks of blue bone gel sparking off the bone!" + else + msg += "!" + return "[msg.Join()]" + +/* + New common procs for /datum/wound/blunt/ +*/ + +/datum/wound/blunt/proc/update_inefficiencies() + if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(limb.current_gauze) + limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor + else + limp_slowdown = initial(limp_slowdown) + victim.apply_status_effect(STATUS_EFFECT_LIMP) + else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + if(limb.current_gauze) + interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_gauze.splint_factor) + else + interaction_efficiency_penalty = interaction_efficiency_penalty + + if(initial(disabling)) + set_disabling(!limb.current_gauze) + + limb.update_wounds() + +/// Joint Dislocation (Moderate Blunt) + +/datum/wound/blunt/moderate + name = "Joint Dislocation" + desc = "Patient's bone has been unset from socket, causing pain and reduced motor function." + treat_text = "Recommended application of bonesetter to affected limb, though manual relocation by applying an aggressive grab to the patient and helpfully interacting with afflicted limb may suffice." + examine_desc = "is awkwardly jammed out of place" + occur_text = "jerks violently and becomes unseated" + severity = WOUND_SEVERITY_MODERATE + viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + interaction_efficiency_penalty = 1.5 + limp_slowdown = 3 + threshold_minimum = 35 + threshold_penalty = 15 + treatable_tool = TOOL_BONESET + wound_flags = (BONE_WOUND) + status_effect_type = /datum/status_effect/wound/blunt/moderate + scar_keyword = "bluntmoderate" + +/datum/wound/blunt/moderate/crush() + if(prob(33)) + victim.visible_message("[victim]'s dislocated [limb.name] pops back into place!", "Your dislocated [limb.name] pops back into place! Ow!") + remove_wound() + +/datum/wound/blunt/moderate/try_handling(mob/living/carbon/human/user) + if(user.pulling != victim || user.zone_selected != limb.body_zone || user.a_intent == INTENT_GRAB) + return FALSE + + if(user.grab_state == GRAB_PASSIVE) + to_chat(user, "You must have [victim] in an aggressive grab to manipulate [victim.p_their()] [lowertext(name)]!") + return TRUE + + if(user.grab_state >= GRAB_AGGRESSIVE) + user.visible_message("[user] begins twisting and straining [victim]'s dislocated [limb.name]!", "You begin twisting and straining [victim]'s dislocated [limb.name]...", ignored_mobs=victim) + to_chat(victim, "[user] begins twisting and straining your dislocated [limb.name]!") + if(user.a_intent == INTENT_HELP) + chiropractice(user) + else + malpractice(user) + return TRUE + +/// If someone is snapping our dislocated joint back into place by hand with an aggro grab and help intent +/datum/wound/blunt/moderate/proc/chiropractice(mob/living/carbon/human/user) + var/time = base_treat_time + + if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, .proc/still_exists))) + return + + if(prob(65)) + user.visible_message("[user] snaps [victim]'s dislocated [limb.name] back into place!", "You snap [victim]'s dislocated [limb.name] back into place!", ignored_mobs=victim) + to_chat(victim, "[user] snaps your dislocated [limb.name] back into place!") + victim.emote("scream") + limb.receive_damage(brute=20, wound_bonus=CANT_WOUND) + qdel(src) + else + user.visible_message("[user] wrenches [victim]'s dislocated [limb.name] around painfully!", "You wrench [victim]'s dislocated [limb.name] around painfully!", ignored_mobs=victim) + to_chat(victim, "[user] wrenches your dislocated [limb.name] around painfully!") + limb.receive_damage(brute=10, wound_bonus=CANT_WOUND) + chiropractice(user) + +/// If someone is snapping our dislocated joint into a fracture by hand with an aggro grab and harm or disarm intent +/datum/wound/blunt/moderate/proc/malpractice(mob/living/carbon/human/user) + var/time = base_treat_time + + if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, .proc/still_exists))) + return + + if(prob(65)) + user.visible_message("[user] snaps [victim]'s dislocated [limb.name] with a sickening crack!", "You snap [victim]'s dislocated [limb.name] with a sickening crack!", ignored_mobs=victim) + to_chat(victim, "[user] snaps your dislocated [limb.name] with a sickening crack!") + victim.emote("scream") + limb.receive_damage(brute=25, wound_bonus=30) + else + user.visible_message("[user] wrenches [victim]'s dislocated [limb.name] around painfully!", "You wrench [victim]'s dislocated [limb.name] around painfully!", ignored_mobs=victim) + to_chat(victim, "[user] wrenches your dislocated [limb.name] around painfully!") + limb.receive_damage(brute=10, wound_bonus=CANT_WOUND) + malpractice(user) + + +/datum/wound/blunt/moderate/treat(obj/item/I, mob/user) + if(victim == user) + victim.visible_message("[user] begins resetting [victim.p_their()] [limb.name] with [I].", "You begin resetting your [limb.name] with [I]...") + else + user.visible_message("[user] begins resetting [victim]'s [limb.name] with [I].", "You begin resetting [victim]'s [limb.name] with [I]...") + + if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, .proc/still_exists))) + return + + if(victim == user) + limb.receive_damage(brute=15, wound_bonus=CANT_WOUND) + victim.visible_message("[user] finishes resetting [victim.p_their()] [limb.name]!", "You reset your [limb.name]!") + else + limb.receive_damage(brute=10, wound_bonus=CANT_WOUND) + user.visible_message("[user] finishes resetting [victim]'s [limb.name]!", "You finish resetting [victim]'s [limb.name]!", victim) + to_chat(victim, "[user] resets your [limb.name]!") + + victim.emote("scream") + qdel(src) + +/* + Severe (Hairline Fracture) +*/ + +/datum/wound/blunt/severe + name = "Hairline Fracture" + desc = "Patient's bone has suffered a crack in the foundation, causing serious pain and reduced limb functionality." + treat_text = "Recommended light surgical application of bone gel, though a sling of medical gauze will prevent worsening situation." + examine_desc = "appears grotesquely swollen, its attachment weakened" + occur_text = "sprays chips of bone and develops a nasty looking bruise" + + severity = WOUND_SEVERITY_SEVERE + interaction_efficiency_penalty = 2 + limp_slowdown = 6 + threshold_minimum = 60 + threshold_penalty = 30 + treatable_by = list(/obj/item/stack/medical/bone_gel) + status_effect_type = /datum/status_effect/wound/blunt/severe + scar_keyword = "bluntsevere" + brain_trauma_group = BRAIN_TRAUMA_MILD + trauma_cycle_cooldown = 1.5 MINUTES + internal_bleeding_chance = 40 + wound_flags = (BONE_WOUND | ACCEPTS_GAUZE | MANGLES_BONE) + regen_ticks_needed = 120 // ticks every 2 seconds, 240 seconds, so roughly 4 minutes default + +/// Compound Fracture (Critical Blunt) +/datum/wound/blunt/critical + name = "Compound Fracture" + desc = "Patient's bones have suffered multiple gruesome fractures, causing significant pain and near uselessness of limb." + treat_text = "Immediate binding of affected limb, followed by surgical intervention ASAP." + examine_desc = "is mangled and pulped, seemingly held together by tissue alone" + occur_text = "cracks apart, exposing broken bones to open air" + + severity = WOUND_SEVERITY_CRITICAL + interaction_efficiency_penalty = 4 + limp_slowdown = 9 + sound_effect = 'sound/effects/wounds/crack2.ogg' + threshold_minimum = 115 + threshold_penalty = 50 + disabling = TRUE + treatable_by = list(/obj/item/stack/medical/bone_gel) + status_effect_type = /datum/status_effect/wound/blunt/critical + scar_keyword = "bluntcritical" + brain_trauma_group = BRAIN_TRAUMA_SEVERE + trauma_cycle_cooldown = 2.5 MINUTES + internal_bleeding_chance = 60 + wound_flags = (BONE_WOUND | ACCEPTS_GAUZE | MANGLES_BONE) + regen_ticks_needed = 240 // ticks every 2 seconds, 480 seconds, so roughly 8 minutes default + +// doesn't make much sense for "a" bone to stick out of your head +/datum/wound/blunt/critical/apply_wound(obj/item/bodypart/L, silent, datum/wound/old_wound, smited) + if(L.body_zone == BODY_ZONE_HEAD) + occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood" + examine_desc = "has an unsettling indent, with bits of skull poking out" + . = ..() + +/// if someone is using bone gel on our wound +/datum/wound/blunt/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user) + if(gelled) + to_chat(user, "[user == victim ? "Your" : "[victim]'s"] [limb.name] is already coated with bone gel!") + return + + user.visible_message("[user] begins hastily applying [I] to [victim]'s' [limb.name]...", "You begin hastily applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name], disregarding the warning label...") + + if(!do_after(user, base_treat_time * 1.5 * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, .proc/still_exists))) + return + + I.use(1) + victim.emote("scream") + if(user != victim) + user.visible_message("[user] finishes applying [I] to [victim]'s [limb.name], emitting a fizzing noise!", "You finish applying [I] to [victim]'s [limb.name]!", ignored_mobs=victim) + to_chat(victim, "[user] finishes applying [I] to your [limb.name], and you can feel the bones exploding with pain as they begin melting and reforming!") + else + var/painkiller_bonus = 0 + if(victim.drunkenness > 10) + painkiller_bonus += 10 + if(victim.reagents?.has_reagent(/datum/reagent/medicine/morphine)) + painkiller_bonus += 20 + if(victim.reagents?.has_reagent(/datum/reagent/determination)) + painkiller_bonus += 10 + + if(prob(25 + (20 * (severity - 2)) - painkiller_bonus)) // 25%/45% chance to fail self-applying with severe and critical wounds, modded by painkillers + victim.visible_message("[victim] fails to finish applying [I] to [victim.p_their()] [limb.name], passing out from the pain!", "You black out from the pain of applying [I] to your [limb.name] before you can finish!") + victim.AdjustUnconscious(5 SECONDS) + return + regen_ticks_needed *= 1.5 + victim.visible_message("[victim] finishes applying [I] to [victim.p_their()] [limb.name], grimacing from the pain!", "You finish applying [I] to your [limb.name], and your bones explode in pain!") + + limb.receive_damage(25, stamina=100, wound_bonus=CANT_WOUND) + if(!gelled) + gelled = TRUE + taped = TRUE + processes = TRUE + +/// if someone is using surgical tape on our wound +/*datum/wound/blunt/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user) + if(!gelled) + to_chat(user, "[user == victim ? "Your" : "[victim]'s"] [limb.name] must be coated with bone gel to perform this emergency operation!") + return + if(taped) + to_chat(user, "[user == victim ? "Your" : "[victim]'s"] [limb.name] is already wrapped in [I.name] and reforming!") + return + + user.visible_message("[user] begins applying [I] to [victim]'s' [limb.name]...", "You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name]...") + + if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, .proc/still_exists))) + return + + if(victim == user) + regen_ticks_needed *= 1.5 + I.use(1) + if(user != victim) + user.visible_message("[user] finishes applying [I] to [victim]'s [limb.name], emitting a fizzing noise!", "You finish applying [I] to [victim]'s [limb.name]!", ignored_mobs=victim) + to_chat(victim, "[user] finishes applying [I] to your [limb.name], you immediately begin to feel your bones start to reform!") + else + victim.visible_message("[victim] finishes applying [I] to [victim.p_their()] [limb.name], !", "You finish applying [I] to your [limb.name], and you immediately begin to feel your bones start to reform!") + + taped = TRUE + processes = TRUE*/ + +/datum/wound/blunt/treat(obj/item/I, mob/user) + if(istype(I, /obj/item/stack/medical/bone_gel)) + gel(I, user) + /*else if(istype(I, /obj/item/stack/sticky_tape/surgical)) + tape(I, user)*/ + +/datum/wound/blunt/get_scanner_description(mob/user) + . = ..() + + . += "