From 2e939e4d9b09b0b715ca145428b93bd391be0f09 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 4 Aug 2025 08:29:41 -0400 Subject: [PATCH] Galatean BioAugments (#21013) This PR "Gently refactors" organs in preparation for adding in new variant organsas requested by HumanLore. Additionally, it adds in several organs that were requested by Human Lore, as described in: https://docs.google.com/document/d/1SLbll969PyJ_H9J1LP8cmdso9OMozUc8MGOVZkY4HbE/mobilebasic The new augments include: Auxiliary Heart, Platelet Factories, and Subdermal Carpace Additionally, all of the new Galatean Bioaugs have been added to the antag uplink. The bulk of this PR consists of replacing a majority of all the organ "Magic Numbers" with variables which could feasibly be modified by any new kind of organ object. Additionally-- although it's unused in this PR, here's also a new optional boolean for hearts to create a "Fake Pulse", which would be useful in the future for event character shells to be able to fool a pulse check. Finally, the heart systems are configured to use Signals so that arbitrarily any component can introduce their own modifiers to the heart statistics. This PR was requested by Human Lore: image Sprites have been made by @NobleRow --- aurorastation.dme | 3 + code/__DEFINES/dcs/signals.dm | 1 - code/__DEFINES/mobs.dm | 71 +++--- code/__DEFINES/organs.dm | 39 +++ code/datums/uplink/implants.dm | 35 +++ .../items/devices/augment_implanter.dm | 15 ++ .../loadout/items/augments.dm | 68 ++++- .../preference_setup/loadout/loadout.dm | 8 + code/modules/mob/living/carbon/human/human.dm | 2 +- code/modules/organs/blood.dm | 98 ++++---- code/modules/organs/internal/_internal.dm | 21 +- code/modules/organs/internal/appendix.dm | 66 +++-- code/modules/organs/internal/heart.dm | 233 +++++++++++++++--- code/modules/organs/internal/liver.dm | 192 +++++++++++---- .../organs/subtypes/augment/augment.dm | 13 +- .../augment/augments/auxiliary_heart.dm | 34 +++ .../organs/subtypes/augment/augments/fluff.dm | 19 ++ .../subtypes/augment/augments/mind_blanker.dm | 63 +++-- .../augment/augments/platelet_factories.dm | 42 ++++ .../augment/augments/subdermal_carapace.dm | 30 +++ code/modules/psionics/mob/mob_helpers.dm | 5 +- html/changelogs/hellfirejag-heartrefactor.yml | 7 + icons/obj/organs/bioaugs.dmi | Bin 0 -> 2120 bytes 23 files changed, 817 insertions(+), 248 deletions(-) create mode 100644 code/modules/organs/subtypes/augment/augments/auxiliary_heart.dm create mode 100644 code/modules/organs/subtypes/augment/augments/platelet_factories.dm create mode 100644 code/modules/organs/subtypes/augment/augments/subdermal_carapace.dm create mode 100644 html/changelogs/hellfirejag-heartrefactor.yml create mode 100644 icons/obj/organs/bioaugs.dmi diff --git a/aurorastation.dme b/aurorastation.dme index 4b56880d3b2..8dfe3b76cb4 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -3057,6 +3057,7 @@ #include "code\modules\organs\subtypes\zombie.dm" #include "code\modules\organs\subtypes\augment\augment.dm" #include "code\modules\organs\subtypes\augment\augment_tool.dm" +#include "code\modules\organs\subtypes\augment\augments\auxiliary_heart.dm" #include "code\modules\organs\subtypes\augment\augments\cohlear.dm" #include "code\modules\organs\subtypes\augment\augments\combitool.dm" #include "code\modules\organs\subtypes\augment\augments\corrective_lens.dm" @@ -3078,8 +3079,10 @@ #include "code\modules\organs\subtypes\augment\augments\mind_blanker.dm" #include "code\modules\organs\subtypes\augment\augments\pen.dm" #include "code\modules\organs\subtypes\augment\augments\phalanx_facial_plate.dm" +#include "code\modules\organs\subtypes\augment\augments\platelet_factories.dm" #include "code\modules\organs\subtypes\augment\augments\psi_receiver.dm" #include "code\modules\organs\subtypes\augment\augments\sight_lights.dm" +#include "code\modules\organs\subtypes\augment\augments\subdermal_carapace.dm" #include "code\modules\organs\subtypes\augment\augments\suspension.dm" #include "code\modules\organs\subtypes\augment\augments\synthetic_cords.dm" #include "code\modules\organs\subtypes\augment\augments\taste_booster.dm" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 23388249998..071b7ebfc4c 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -67,6 +67,5 @@ // Psionics signals /// Raised on the target of a "mind-affecting" psionic power. #define COMSIG_PSI_MIND_POWER "psi_block_check" - #define COMSIG_PSI_MIND_POWER_CANCELLED (1<<0) /*******Component Specific Signals*******/ diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 46a26c33b37..0a870f95756 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -150,40 +150,43 @@ #define BP_TUMOUR_SPREADING "malignant tumour" //Augment organs -#define BP_AUG_ACC_CORDS "modified synthetic vocal cords" -#define BP_AUG_AIR_ANALYZER "integrated air analyzer" -#define BP_AUG_CALF_OVERRIDE "calf overdrive" -#define BP_AUG_COCHLEAR "cochlear implant" -#define BP_AUG_CORDS "synthetic vocal cords" -#define BP_AUG_CORRECTIVE_LENS "corrective lenses" -#define BP_AUG_CRAYON "retractable crayon" -#define BP_AUG_CYBORG_ANALYZER "retractable cyborg analyzer" -#define BP_AUG_DRILL "integrated mining drill" -#define BP_AUG_EMOTION "emotional manipulator" -#define BP_AUG_ENCHANED_VISION "vision enhanced retinas" -#define BP_AUG_ETHANOL_BURNER "integrated ethanol burner" -#define BP_AUG_EYE_SENSORS "integrated eyes sensors" -#define BP_AUG_FUEL_CELL "integrated fuel cell" -#define BP_AUG_GLARE_DAMPENER "glare dampeners" -#define BP_AUG_GUSTATORIAL "integrated gustatorial centre" -#define BP_AUG_HAIR "synthetic hair extensions" -#define BP_AUG_HEALTHSCAN "integrated health scanner" -#define BP_AUG_LANGUAGE "integrated language processor" -#define BP_AUG_LIGHTER "retractable lighter" -#define BP_AUG_MAGBOOT "integrated mag-claws" -#define BP_AUG_MEMORY "memory inhibitor" -#define BP_AUG_MIND_BLANKER "mind blanker" -#define BP_AUG_MIND_BLANKER_L "lethal mind blanker" -#define BP_AUG_PEN "retractable combipen" -#define BP_AUG_PSI "psionic receiver" -#define BP_AUG_RADIO "integrated radio" -#define BP_AUG_SIGHTLIGHTS "ocular installed sightlights" -#define BP_AUG_SUSPENSION "calf suspension" -#define BP_AUG_TASTE_BOOSTER "taste booster" -#define BP_AUG_TESLA "tesla spine" -#define BP_AUG_TIMEPIECE "integrated timepiece" -#define BP_AUG_TRANSLATOR "universal translator" -#define BP_AUG_TOOL "retractable combitool" +#define BP_AUG_ACC_CORDS "modified synthetic vocal cords" +#define BP_AUG_AIR_ANALYZER "integrated air analyzer" +#define BP_AUG_AUX_HEART "auxiliary heart" +#define BP_AUG_CALF_OVERRIDE "calf overdrive" +#define BP_AUG_COCHLEAR "cochlear implant" +#define BP_AUG_CORDS "synthetic vocal cords" +#define BP_AUG_CORRECTIVE_LENS "corrective lenses" +#define BP_AUG_CRAYON "retractable crayon" +#define BP_AUG_CYBORG_ANALYZER "retractable cyborg analyzer" +#define BP_AUG_DRILL "integrated mining drill" +#define BP_AUG_EMOTION "emotional manipulator" +#define BP_AUG_ENCHANED_VISION "vision enhanced retinas" +#define BP_AUG_ETHANOL_BURNER "integrated ethanol burner" +#define BP_AUG_EYE_SENSORS "integrated eyes sensors" +#define BP_AUG_FUEL_CELL "integrated fuel cell" +#define BP_AUG_GLARE_DAMPENER "glare dampeners" +#define BP_AUG_GUSTATORIAL "integrated gustatorial centre" +#define BP_AUG_HAIR "synthetic hair extensions" +#define BP_AUG_HEALTHSCAN "integrated health scanner" +#define BP_AUG_LANGUAGE "integrated language processor" +#define BP_AUG_LIGHTER "retractable lighter" +#define BP_AUG_MAGBOOT "integrated mag-claws" +#define BP_AUG_MEMORY "memory inhibitor" +#define BP_AUG_MIND_BLANKER "mind blanker" +#define BP_AUG_MIND_BLANKER_L "lethal mind blanker" +#define BP_AUG_PEN "retractable combipen" +#define BP_AUG_PLATELET_FACTORIES "platelet factories" +#define BP_AUG_PSI "psionic receiver" +#define BP_AUG_RADIO "integrated radio" +#define BP_AUG_SIGHTLIGHTS "ocular installed sightlights" +#define BP_AUG_SUBDERMAL_CARAPACE "subdermal carapace" +#define BP_AUG_SUSPENSION "calf suspension" +#define BP_AUG_TASTE_BOOSTER "taste booster" +#define BP_AUG_TESLA "tesla spine" +#define BP_AUG_TIMEPIECE "integrated timepiece" +#define BP_AUG_TRANSLATOR "universal translator" +#define BP_AUG_TOOL "retractable combitool" //Organ defines #define PROCESS_ACCURACY 10 diff --git a/code/__DEFINES/organs.dm b/code/__DEFINES/organs.dm index bc61ad9d535..a4cd919446e 100644 --- a/code/__DEFINES/organs.dm +++ b/code/__DEFINES/organs.dm @@ -3,3 +3,42 @@ #define SOCKET_UNSHIELDED 0 #define SOCKET_SHIELDED 1 #define SOCKET_FULLSHIELDED 2 + +// Heart signals +/** + * Raised on an entity whose heart is attempting to pump blood. + * The original heart is passed in and null checked, so Registers won't need to check it elsewhere. + * I have no way of making the heart variables private set, so the onus is on you dear contributor not to set them by event. I've set aside three variables for you to touch instead. + * "recent_pump" controls interactions with things like CPR, stabilizer harness + * "pulse_mod" at this step is equal to 1x, use this to insert any desired pump rate modifications. + * "min_efficiency" controls the Floor of heart pump rate, particularly when its stopped. Default is 0.5x with CPR, 0.3x without + */ +#define COMSIG_HEART_PUMP_EVENT "heart_pump_event" + +/** + * Raised on an entity whose blood is attempting to circulate oxygen through the body. + * "blood_volume" is the percentage of blood vs max blood, between 0 and 100. Don't put it outside those bounds. + * "blood_volume_mod" is the ratio of oxygen loss to health loss, representing how damaged the circulatory system is as a whole. You can safely multiply against this to include your own modifiers. + * "oxygenated_add" represents the ratio of internal blood oxygenation to external oxygenation. Such as from Dexalin and Dexplus. Defaults to 0, so additive identity applies. + */ +#define COMSIG_BLOOD_OXYGENATION_EVENT "blood_oxygenation_event" + +/** + * Raised on a heart that is calculating its pulse. + * "pulse_mod" Additive modifier on the pulse rate, which is an integer. Tightly control this and use it miserly. + * "is_stable" boolean used by Inaprovaline, but can be used here by anything you want. + * "oxy" ratio of blood oxygenation to blood volume. + * "circulation" the percentage of blood remaining in the body from 0 to 100. + */ +#define COMSIG_HEART_PULSE_EVENT "heart_pulse_event" + +/** + * Raised on a heart that is calculating the effects of bleeds. + */ +#define COMSIG_HEART_BLEED_EVENT "heart_bleed_event" + +// Liver signals +/** + * Raised on an entity whose liver is attempting to filter blood. + */ +#define COMSIG_LIVER_FILTER_EVENT "liver_filter_event" diff --git a/code/datums/uplink/implants.dm b/code/datums/uplink/implants.dm index 74fbc2ec54a..0a86c0dbe83 100644 --- a/code/datums/uplink/implants.dm +++ b/code/datums/uplink/implants.dm @@ -68,3 +68,38 @@ desc = "A box containing a telefreedom full kit, which allows you to teleport to a linked telepad that you \ will build once. Comes with everything you need for 4 people. You supply the screwdriver and have to build it." path = /obj/item/storage/box/telefreedom_kit + +/datum/uplink_item/item/implants/auxiliary_heart + name = "Galatean Auxiliary Heart Implanter" + bluecrystal_cost = 3 + telecrystal_cost = 3 + desc = "An augment implanter, with a Auxiliary Heart bioaug. ONLY WORKS ON HUMANS!" + path = /obj/item/device/augment_implanter/auxiliary_heart + +/datum/uplink_item/item/implants/mind_blanker + name = "Galatean Mind Blanker Implanter" + bluecrystal_cost = 2 + telecrystal_cost = 2 + desc = "An augment implanter, with a Mind Blanker bioaug. ONLY WORKS ON HUMANS!" + path = /obj/item/device/augment_implanter/mind_blanker + +/datum/uplink_item/item/implants/mind_blanker_lethal + name = "Galatean Mind Blanker (Lethal) Implanter" + bluecrystal_cost = 4 + telecrystal_cost = 4 + desc = "An augment implanter, with a Lethal Mind Blanker bioaug. ONLY WORKS ON HUMANS!" + path = /obj/item/device/augment_implanter/mind_blanker_lethal + +/datum/uplink_item/item/implants/platelet_factories + name = "Galatean Platelet Factories Implanter" + bluecrystal_cost = 3 + telecrystal_cost = 3 + desc = "An augment implanter, with a Platelet Factories bioaug. ONLY WORKS ON HUMANS!" + path = /obj/item/device/augment_implanter/platelet_factories + +/datum/uplink_item/item/implants/subdermal_carapace + name = "Galatean Subdermal Carapace Implanter" + bluecrystal_cost = 5 + telecrystal_cost = 5 + desc = "An augment implanter, with a Subdermal Carapace bioaug. ONLY WORKS ON HUMANS!" + path = /obj/item/device/augment_implanter/subdermal_carapace diff --git a/code/game/objects/items/devices/augment_implanter.dm b/code/game/objects/items/devices/augment_implanter.dm index fc0036f8891..ccef5e89005 100644 --- a/code/game/objects/items/devices/augment_implanter.dm +++ b/code/game/objects/items/devices/augment_implanter.dm @@ -76,3 +76,18 @@ /obj/item/device/augment_implanter/hivenet_warfare new_augment = /obj/item/organ/internal/augment/hiveshield/warfare + +/obj/item/device/augment_implanter/auxiliary_heart + new_augment = /obj/item/organ/internal/augment/bioaug/auxiliary_heart + +/obj/item/device/augment_implanter/mind_blanker + new_augment = /obj/item/organ/internal/augment/bioaug/mind_blanker + +/obj/item/device/augment_implanter/mind_blanker_lethal + new_augment = /obj/item/organ/internal/augment/bioaug/mind_blanker_lethal + +/obj/item/device/augment_implanter/platelet_factories + new_augment = /obj/item/organ/internal/augment/bioaug/platelet_factories + +/obj/item/device/augment_implanter/subdermal_carapace + new_augment = /obj/item/organ/internal/augment/bioaug/subdermal_carapace diff --git a/code/modules/client/preference_setup/loadout/items/augments.dm b/code/modules/client/preference_setup/loadout/items/augments.dm index b6397431e97..09ed213c2dc 100644 --- a/code/modules/client/preference_setup/loadout/items/augments.dm +++ b/code/modules/client/preference_setup/loadout/items/augments.dm @@ -299,17 +299,75 @@ cost = 4 allowed_roles = list("Physician", "Surgeon", "Pharmacist", "Paramedic", "Psychiatrist", "Medical Intern", "Medical Personnel", "Security Officer", "Warden", "Security Cadet", "Investigator", "Security Personnel", "Corporate Liaison", "Assistant", "Off-Duty Crew Member", "Corporate Reporter", "Bridge Crew") +/datum/gear/augment/auxiliary_heart + display_name = "Auxiliary Heart" + description = "Primarily intended for soldiers and the elderly, the auxiliary heart is a small secondary heart implanted below the original. Should the original heart shut down, the secondary heart will activate, keeping the user alive until the original can be restarted or replaced." + path = /obj/item/organ/internal/augment/bioaug/auxiliary_heart + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + cost = 2 + +/datum/gear/augment/boosted_heart + display_name = "Boosted Heart" + description = "Intended for athletes, some workers, and soldiers, this improved heart increases blood flow and circulation. It provides an improvement to blood oxygenation and stamina, at the cost of requiring more food and water. Outside of Galatea, this augment is popular among professional athletes." + path = /obj/item/organ/internal/heart/boosted_heart + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + cost = 3 + +/datum/gear/augment/boosted_liver + display_name = "Boosted Liver" + description = "Designed primarily for diplomats or Galateans abroad, the boosted liver improves toxin filtering, giving a resistance to toxin damage. As a consequence, it makes it impossible for the user to get drunk." + path = /obj/item/organ/internal/liver/boosted_liver + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + cost = 2 + /datum/gear/augment/mind_blanker display_name = "Galatean Mind Blanker" description = "A small, discrete organ attached near the base of the brainstem. Any attempt to read the mind of an individual with this augment installed will fail, as will attempts at psychic brainwashing." - path = /obj/item/organ/internal/augment/mind_blanker + path = /obj/item/organ/internal/augment/bioaug/mind_blanker origin_restriction = list(/singleton/origin_item/origin/galatea) - cost = 4 + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + cost = 2 /datum/gear/augment/mind_blanker_lethal display_name = "Galatean Mind Blanker (Lethal)" description = "Available only to higher-up MfAS agents and members of the Galatean government. This enhanced variant of a mind blanker introduces a psionic trap which inflicts severe neural damage on anyone attempting to read the user's mind." - path = /obj/item/organ/internal/augment/mind_blanker_lethal + path = /obj/item/organ/internal/augment/bioaug/mind_blanker_lethal origin_restriction = list(/singleton/origin_item/origin/galatea) - cost = 6 - allowed_roles = list("Consular") + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + cost = 4 + allowed_roles = list("Consular Officer") + +/datum/gear/augment/bio_head_fluff + display_name = "custom head bioaug" + description = "A fluff based bioaug that can be renamed/redescribed to appear as something else for RP purposes." + path = /obj/item/organ/internal/augment/bioaug/head_fluff + flags = GEAR_HAS_NAME_SELECTION | GEAR_HAS_DESC_SELECTION + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + +/datum/gear/augment/bio_chest_fluff + display_name = "custom chest bioaug" + description = "A fluff based bioaug that can be renamed/redescribed to appear as something else for RP purposes." + path = /obj/item/organ/internal/augment/bioaug/head_fluff/chest_fluff + flags = GEAR_HAS_NAME_SELECTION | GEAR_HAS_DESC_SELECTION + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + +/datum/gear/augment/bio_rhand_fluff + display_name = "custom right hand bioaug" + description = "A fluff based bioaug that can be renamed/redescribed to appear as something else for RP purposes." + path = /obj/item/organ/internal/augment/bioaug/head_fluff/rhand_fluff + flags = GEAR_HAS_NAME_SELECTION | GEAR_HAS_DESC_SELECTION + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) + +/datum/gear/augment/bio_lhand_fluff + display_name = "custom left hand bioaug" + description = "A fluff based bioaug that can be renamed/redescribed to appear as something else for RP purposes." + path = /obj/item/organ/internal/augment/bioaug/head_fluff/lhand_fluff + flags = GEAR_HAS_NAME_SELECTION | GEAR_HAS_DESC_SELECTION + origin_restriction = list(/singleton/origin_item/origin/galatea) + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD) diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm index b761cee56e5..d64c3126b77 100644 --- a/code/modules/client/preference_setup/loadout/loadout.dm +++ b/code/modules/client/preference_setup/loadout/loadout.dm @@ -574,6 +574,14 @@ GLOBAL_LIST_INIT(gear_datums, list()) replaced_limb.droplimb(TRUE, DROPLIMB_EDGE, FALSE) qdel(replaced_limb) + // Handling for "Organ swapping" augments. Anything that shares an organ tag with a pre-existing organ. + if(ispath(spawn_path, /obj/item/organ/internal)) + var/obj/item/organ/internal/internal_aug = spawn_path + var/obj/item/organ/internal/replaced_organ = H.get_organ(initial(internal_aug.organ_tag)) + if(internal_aug.organ_tag == replaced_organ.organ_tag) + replaced_organ.removed(H, null) + qdel(replaced_organ) + var/item = new spawn_path(spawn_location) for(var/datum/gear_tweak/gt in gear_tweaks) if(metadata["[gt]"]) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index fd76ca08866..faa63599f60 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1996,7 +1996,7 @@ /mob/living/carbon/human/proc/pulse() var/obj/item/organ/internal/heart/heart = internal_organs_by_name[BP_HEART] - return heart ? heart.pulse : PULSE_NONE + return heart ? heart.fake_pulse ? PULSE_NORM : heart.pulse : PULSE_NONE /mob/living/carbon/human/proc/move_to_stomach(atom/movable/victim) var/obj/item/organ/internal/stomach/stomach = internal_organs_by_name[BP_STOMACH] diff --git a/code/modules/organs/blood.dm b/code/modules/organs/blood.dm index e2166a657b8..ef21e37f1fa 100644 --- a/code/modules/organs/blood.dm +++ b/code/modules/organs/blood.dm @@ -27,97 +27,99 @@ //Makes a blood drop, leaking amt units of blood from the mob /mob/living/carbon/human/proc/drip(var/amt as num, var/tar = src, var/spraydir) - if(species && species.flags & NO_BLOOD) //TODO: Make drips come from the reagents instead. - return - - if(!amt) + if(!amt || species && species.flags & NO_BLOOD) //TODO: Make drips come from the reagents instead. return vessel.remove_reagent(/singleton/reagent/blood,amt) blood_splatter(tar, src, spray_dir = spraydir) -#define BLOOD_SPRAY_DISTANCE 2 -/mob/living/carbon/human/proc/blood_squirt(var/amt, var/turf/sprayloc) +/mob/living/carbon/human/proc/blood_squirt(var/amt, var/turf/sprayloc, var/blood_spray_distance) if(amt <= 0 || !istype(sprayloc)) return var/spraydir = pick(GLOB.alldirs) - amt = Ceiling(amt/BLOOD_SPRAY_DISTANCE) + amt = Ceiling(amt/blood_spray_distance) var/bled = 0 - for(var/i = 1 to BLOOD_SPRAY_DISTANCE) + for(var/i = 1 to blood_spray_distance) sprayloc = get_step(sprayloc, spraydir) if(!istype(sprayloc) || (sprayloc.density && !iswall(sprayloc))) break var/hit_mob for(var/thing in sprayloc) var/atom/A = thing - if(!A.simulated) + if(!A.simulated || !ishuman(A) || !(A.CanPass(src, sprayloc)) || hit_mob) continue - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(!H.lying) - H.bloody_body(src) - H.bloody_hands(src) - var/blinding = FALSE - if(ran_zone(BP_HEAD, 75)) - blinding = TRUE - for(var/obj/item/I in list(H.head, H.glasses, H.wear_mask)) - if(I && (I.body_parts_covered & EYES)) - blinding = FALSE - break - if(blinding) - H.eye_blurry = max(H.eye_blurry, 10) - H.eye_blind = max(H.eye_blind, 5) - to_chat(H, SPAN_DANGER("You are blinded by a spray of blood!")) - else - to_chat(H, SPAN_DANGER("You are hit by a spray of blood!")) - hit_mob = TRUE - - if(!(A.CanPass(src, sprayloc)) || hit_mob) + var/mob/living/carbon/human/H = A + if(H.lying) continue + H.bloody_body(src) + H.bloody_hands(src) + var/blinding = FALSE + if(ran_zone(BP_HEAD, 75)) + blinding = TRUE + for(var/obj/item/I in list(H.head, H.glasses, H.wear_mask)) + if(I && (I.body_parts_covered & EYES)) + blinding = FALSE + break + if(blinding) + H.eye_blurry = max(H.eye_blurry, 10) + H.eye_blind = max(H.eye_blind, 5) + to_chat(H, SPAN_DANGER("You are blinded by a spray of blood!")) + else + to_chat(H, SPAN_DANGER("You are hit by a spray of blood!")) + hit_mob = TRUE + drip(amt, sprayloc, spraydir) bled += amt if(hit_mob || iswall(sprayloc)) break return bled -#undef BLOOD_SPRAY_DISTANCE /mob/living/carbon/human/proc/get_blood_volume() return round((REAGENT_VOLUME(vessel, /singleton/reagent/blood)/species.blood_volume)*100) /mob/living/carbon/human/proc/get_blood_circulation() var/obj/item/organ/internal/heart/heart = internal_organs_by_name[BP_HEART] + var/blood_volume = get_blood_volume() + if(!heart) return 0.25 * blood_volume var/recent_pump = LAZYACCESS(heart.external_pump, 1) > world.time - (20 SECONDS) - var/pulse_mod = 1 + var/pulse_mod = heart.base_pump_rate + var/min_efficiency = recent_pump ? 0.5 : 0.3 + + // Check if any components on the user wish to mess with the pump rate. + SEND_SIGNAL(src, COMSIG_HEART_PUMP_EVENT, heart, &blood_volume, &recent_pump, &pulse_mod, &min_efficiency) + if((status_flags & FAKEDEATH) || BP_IS_ROBOTIC(heart)) - pulse_mod = 1 + pulse_mod = heart.norm_pump_modifier else switch(heart.pulse) if(PULSE_NONE) if(recent_pump) pulse_mod = LAZYACCESS(heart.external_pump, 2) else - pulse_mod *= 0.25 + pulse_mod *= heart.none_pump_modifier if(PULSE_SLOW) - pulse_mod *= 0.9 + pulse_mod *= heart.slow_pump_modifier + if(PULSE_NORM) + pulse_mod *= heart.norm_pump_modifier if(PULSE_FAST) - pulse_mod *= 1.1 - if(PULSE_2FAST, PULSE_THREADY) - pulse_mod *= 1.25 - blood_volume *= pulse_mod - - var/min_efficiency = recent_pump ? 0.5 : 0.3 - blood_volume *= max(min_efficiency, (1-(heart.damage / heart.max_damage))) + pulse_mod *= heart.fast_pump_modifier + if(PULSE_2FAST) + pulse_mod *= heart.too_fast_pump_modifier + if(PULSE_THREADY) + pulse_mod *= heart.thready_pump_modifier + blood_volume *= pulse_mod * max(min_efficiency, (1-(heart.damage / heart.max_damage))) return min(blood_volume, 100) /mob/living/carbon/human/proc/get_blood_oxygenation() var/blood_volume = get_blood_circulation() + if(is_asystole() || (status_flags & FAKEDEATH)) // Heart is missing or isn't beating and we're not breathing (hardcrit) return min(blood_volume, BLOOD_VOLUME_SURVIVE) @@ -125,12 +127,16 @@ return blood_volume var/blood_volume_mod = max(0, 1 - getOxyLoss()/(species.total_health/2)) - var/oxygenated_mult = 0 + var/oxygenated_add = 0 + + // Check if any components on the user wish to mess with the blood oxygenation. + SEND_SIGNAL(src, COMSIG_BLOOD_OXYGENATION_EVENT, &blood_volume, &blood_volume_mod, &oxygenated_add) + if(chem_effects[CE_OXYGENATED] == 1) // Dexalin. - oxygenated_mult = 0.5 + oxygenated_add += 0.5 else if(chem_effects[CE_OXYGENATED] >= 2) // Dexplus. - oxygenated_mult = 0.8 - blood_volume_mod = blood_volume_mod + oxygenated_mult - (blood_volume_mod * oxygenated_mult) + oxygenated_add += 0.8 + blood_volume_mod = blood_volume_mod + oxygenated_add - (blood_volume_mod * oxygenated_add) blood_volume = blood_volume * blood_volume_mod return min(blood_volume, 100) diff --git a/code/modules/organs/internal/_internal.dm b/code/modules/organs/internal/_internal.dm index 7e8197ae3d6..024c9e86615 100644 --- a/code/modules/organs/internal/_internal.dm +++ b/code/modules/organs/internal/_internal.dm @@ -146,17 +146,16 @@ /obj/item/organ/internal/process(seconds_per_tick) ..() if(istype(owner) && (toxin_type in owner.chem_effects)) - take_damage(owner.chem_effects[toxin_type] * 0.1 * PROCESS_ACCURACY, SPT_PROB(1, seconds_per_tick)) - handle_regeneration() + take_damage(owner.chem_effects[toxin_type] * seconds_per_tick) + handle_regeneration(seconds_per_tick) tick_surge_damage() //Yes, this is intentional. -/obj/item/organ/internal/proc/handle_regeneration() +/obj/item/organ/internal/proc/handle_regeneration(seconds_per_tick) SHOULD_CALL_PARENT(TRUE) - if(damage && !BP_IS_ROBOTIC(src) && istype(owner)) - if(!owner.is_asystole()) - if(!(owner.chem_effects[CE_TOXIN] || (toxin_type in owner.chem_effects))) - var/repair_modifier = owner.chem_effects[CE_ORGANREPAIR] || 0.1 - if(damage < repair_modifier*max_damage) - heal_damage(repair_modifier) - return TRUE // regeneration is allowed - return FALSE // regeneration is prevented + if(!damage || BP_IS_ROBOTIC(src) || !istype(owner) || owner.is_asystole() || (owner.chem_effects[CE_TOXIN] || (toxin_type in owner.chem_effects))) + return FALSE + + var/repair_modifier = owner.chem_effects[CE_ORGANREPAIR] || 0.1 + if(damage < repair_modifier*max_damage) + heal_damage(repair_modifier) + return TRUE // regeneration is allowed diff --git a/code/modules/organs/internal/appendix.dm b/code/modules/organs/internal/appendix.dm index 490fdef474e..0846b8ed391 100644 --- a/code/modules/organs/internal/appendix.dm +++ b/code/modules/organs/internal/appendix.dm @@ -14,38 +14,36 @@ /obj/item/organ/internal/appendix/process() ..() - if(inflamed && owner && !BP_IS_ROBOTIC(src)) - var/can_feel_pain = ORGAN_CAN_FEEL_PAIN(src) - inflamed++ - if(prob(5)) - if(can_feel_pain) - owner.custom_pain("You feel a stinging pain in your abdomen!") - owner.visible_message("\The [owner] winces slightly.") - var/obj/item/organ/external/O = owner.get_organ(parent_organ) - if(O) - O.add_pain(10) - if(inflamed > 200) - if(prob(3)) - take_internal_damage(0.1) - if(can_feel_pain) - owner.visible_message("\The [owner] winces painfully.") - var/obj/item/organ/external/O = owner.get_organ(parent_organ) - if(O) - O.add_pain(25) - owner.adjustToxLoss(1) - if(inflamed > 400) - if(prob(1)) - germ_level += rand(2,6) - owner.vomit() - if(inflamed > 600) - if(prob(1)) - if(can_feel_pain) - owner.custom_pain("You feel a stinging pain in your abdomen!") - owner.Weaken(10) + if(!owner || !inflamed || !BP_IS_ROBOTIC(src)) + return - var/obj/item/organ/external/E = owner.get_organ(parent_organ) - E.sever_artery() - E.germ_level = max(INFECTION_LEVEL_TWO, E.germ_level) - owner.adjustToxLoss(25) - removed() - qdel(src) + var/can_feel_pain = ORGAN_CAN_FEEL_PAIN(src) + inflamed++ + if(can_feel_pain && prob(5)) + owner.custom_pain("You feel a stinging pain in your abdomen!") + owner.visible_message("\The [owner] winces slightly.") + var/obj/item/organ/external/O = owner.get_organ(parent_organ) + if(O) + O.add_pain(10) + if(inflamed > 200 && prob(3)) + take_internal_damage(0.1) + if(can_feel_pain) + owner.visible_message("\The [owner] winces painfully.") + var/obj/item/organ/external/O = owner.get_organ(parent_organ) + if(O) + O.add_pain(25) + owner.adjustToxLoss(1) + if(inflamed > 400 && prob(1)) + germ_level += rand(2,6) + owner.vomit() + if(inflamed > 600 && prob(1)) + if(can_feel_pain) + owner.custom_pain("You feel a stinging pain in your abdomen!") + owner.Weaken(10) + + var/obj/item/organ/external/E = owner.get_organ(parent_organ) + E.sever_artery() + E.germ_level = max(INFECTION_LEVEL_TWO, E.germ_level) + owner.adjustToxLoss(25) + removed() + qdel(src) diff --git a/code/modules/organs/internal/heart.dm b/code/modules/organs/internal/heart.dm index a371f010f7c..ae6455dfa7e 100644 --- a/code/modules/organs/internal/heart.dm +++ b/code/modules/organs/internal/heart.dm @@ -18,16 +18,116 @@ var/next_blood_squirt = 0 var/list/external_pump -/obj/item/organ/internal/heart/process() - if(owner) - handle_pulse() - if(pulse) - if(pulse == PULSE_2FAST && prob(1)) - take_internal_damage(0.5) - if(pulse == PULSE_THREADY && prob(5)) - take_internal_damage(0.5) - handle_heartbeat() - handle_blood() + /** + * The amount of damage a heart takes per second while in fibrillation(red pulse). + * A Heart risks stopping as it takes damage. + */ + var/heart_fibrillation_damage = 0.16666 + + /** + * The amount of damage a heart takes per second while in tachycardia(yellow pulse). + * A Heart risks stopping as it takes damage. + */ + var/heart_tachycardia_damage = 0.033333 + + /// The percent chance each tick that a heart in fibrillation will immediately stop + var/fibrillation_stop_risk = 5 + + /// How much pain is required for a heart attack to be possible. + var/shock_stage_for_fibrillation = 120 + + /// Percent chance of a heart attack when above a certain amount of pain. + var/shock_risk_from_pain = 30 + + /// Over this amount of shock, worsen the heart's condition step by one step + var/first_shock_stage = 30 + + /// Over this amount of shock, worsen the heart's condition step by one step, cumulative with the previous stage. + var/second_shock_stage = 80 + + /// How much this heart modifies the owner's blood regeneration, needed for Bioaugmented hearts + var/blood_regen_modifier = 1 + + /// How much this heart modifies the rate of bloodloss per arterial wound. + var/base_arterial_bloodloss_modifier = 1 + + /// How much this heart modifies the rate of bloodloss per cut wound. + var/base_cut_bloodloss_modifier = 1 + + /// How much this heart modifies nutrition loss while regenerating blood. + var/nutrition_cost_per_blood_regen = 2 + + /// How much this heart modifies hydration loss while regenerating blood. + var/hydration_cost_per_blood_regen = 1 + + /// The temperature at which the heart "Freezes" in Cryostasis. + var/minimum_temperature_to_pump_blood = 170 + + /// The amount of time (in seconds) that pass between arterial sprays if injured as such. + var/time_between_arterial_sprays = 100 + + /// How much this heart modifies the rate of bloodloss from external dripping. This is a more general clotting factor. + var/bleed_drip_modifier = 1 + + /// The threshold of pain for which the player will experience "Heart palpitations", IE: Hearing your own heartbeat. + var/palpitations_threshold = 10 + + /// How much "Slow" pulse modifies bleed rate + var/slow_bleeding_modifier = 0.8 + + /// How much "Fast" pulse modifies bleed rate + var/fast_bleeding_modifier = 1.25 + + /// How much Tachycardia modifies bleed rate + var/too_fast_bleeding_modifier = 1.5 + + /// How much fibrillation modifies bleed rate + var/thready_bleeding_modifier = 1.5 + + /// The base rate at which this heart pumps blood regardless of pulse rate. + var/base_pump_rate = 1 + + /// The "base blood circulation" for if the heart is fully stopped. This can get floored later to as little as 0.075 on default settings. + var/none_pump_modifier = 0.25 + + /// Modifier on how much blood this heart pumps while at a "slow" pulse rate. + var/slow_pump_modifier = 0.9 + + /// Modifier on how much blood this heart pumps at a "normal" pulse rate. + var/norm_pump_modifier = 1 + + /// Modifier on how much blood this heart pumps at a "fast" pulse rate. + var/fast_pump_modifier = 1.1 + + /// Modifier on how much blood this heart pumps at a "2fast" pulse rate. + var/too_fast_pump_modifier = 1.25 + + /// Modifier on how much blood this heart pumps at a "thready" pulse rate. + var/thready_pump_modifier = 1.25 + + /// Useful for high end robotic hearts, whether they have ways of faking the appearance of a pulse so long as they're active. + var/fake_pulse = FALSE + + /// How much damage this heart can take from chemically induced arythmia per instance. + var/damage_from_chemicals = 0.5 + + /// Sound effect used for heartbeat warnings, such as when the user is in pain/risk of dying. + var/heartbeat_sound = 'sound/effects/singlebeat.ogg' + + /// Distance effects for arterial sprays. + var/blood_spray_distance = 2 + +/obj/item/organ/internal/heart/process(seconds_per_tick) + if(!owner) + return ..() + handle_pulse() + if(pulse) + if(pulse == PULSE_2FAST) + take_internal_damage(heart_tachycardia_damage * seconds_per_tick) + if(pulse == PULSE_THREADY) + take_internal_damage(heart_fibrillation_damage * seconds_per_tick) + handle_heartbeat() + handle_blood() ..() /obj/item/organ/internal/heart/proc/handle_pulse() @@ -36,22 +136,29 @@ return // pulse mod starts out as just the chemical effect amount - var/pulse_mod = owner.chem_effects[CE_PULSE] + var/pulse_mod = owner.chem_effects[CE_PULSE] // TODO: Make chems go through signals. var/is_stable = owner.chem_effects[CE_STABLE] + var/oxy = owner.get_blood_oxygenation() + var/circulation = owner.get_blood_circulation() + var/canceled = FALSE - // If you have enough heart chemicals to be over 2, you're likely to take extra damage. + // Check if any components on the user wish to mess with the pulse calculations. + SEND_SIGNAL(owner, COMSIG_HEART_PULSE_EVENT, &pulse_mod, &is_stable, &oxy, &circulation, &canceled) + if(canceled) + return // Oh hey someone stopped my heart from beating via a SIGNAL response! + + // If you have enough heart chemicals to be over 2, take extra damage per tick. if(pulse_mod > 2 && !is_stable) var/damage_chance = (pulse_mod - 2) ** 2 if(prob(damage_chance)) - take_internal_damage(0.5) + take_internal_damage(damage_from_chemicals) // Now pulse mod is impacted by shock stage and other things too - if(owner.shock_stage > 30) + if(owner.shock_stage > first_shock_stage) pulse_mod++ - if(owner.shock_stage > 80) + if(owner.shock_stage > second_shock_stage) pulse_mod++ - var/oxy = owner.get_blood_oxygenation() if(oxy < BLOOD_VOLUME_OKAY) //brain wants us to get moar oxygen pulse_mod++ if(oxy < BLOOD_VOLUME_BAD) //MOAR @@ -65,9 +172,9 @@ if(pulse == PULSE_NONE) return else //and if it's beating, let's see if it should - var/should_stop = prob(80) && owner.get_blood_circulation() < BLOOD_VOLUME_SURVIVE //cardiovascular shock, not enough liquid to pump + var/should_stop = prob(80) && circulation < BLOOD_VOLUME_SURVIVE //cardiovascular shock, not enough liquid to pump should_stop = should_stop || prob(max(0, owner.getBrainLoss() - owner.maxHealth * 0.75)) //brain failing to work heart properly - should_stop = should_stop || (prob(5) && pulse == PULSE_THREADY) //erratic heart patterns, usually caused by oxyloss + should_stop = should_stop || (prob(fibrillation_stop_risk) && pulse == PULSE_THREADY) //erratic heart patterns, usually caused by oxyloss should_stop = should_stop || owner.chem_effects[CE_NOPULSE] if(should_stop) // The heart has stopped due to going into traumatic or cardiovascular shock. to_chat(owner, SPAN_DANGER("Your heart has stopped!")) @@ -78,7 +185,7 @@ pulse = clamp(PULSE_NORM + pulse_mod, PULSE_SLOW, PULSE_2FAST) // If fibrillation, then it can be PULSE_THREADY - var/fibrillation = oxy <= BLOOD_VOLUME_SURVIVE || (prob(30) && owner.shock_stage > 120) + var/fibrillation = oxy <= BLOOD_VOLUME_SURVIVE || (owner.shock_stage > shock_stage_for_fibrillation && prob(shock_risk_from_pain)) if(pulse && fibrillation) //I SAID MOAR OXYGEN pulse = PULSE_THREADY @@ -90,7 +197,7 @@ pulse++ /obj/item/organ/internal/heart/proc/handle_heartbeat() - if(pulse >= PULSE_2FAST || owner.shock_stage >= 10) + if(pulse >= PULSE_2FAST || owner.shock_stage >= palpitations_threshold) //PULSE_THREADY - maximum value for pulse, currently it is 5. //High pulse value corresponds to a fast rate of heartbeat. //Divided by 2, otherwise it is too slow. @@ -103,7 +210,7 @@ if(heartbeat >= rate) heartbeat = 0 - sound_to(owner, sound('sound/effects/singlebeat.ogg', 0, 0, 0, 50)) + sound_to(owner, sound(heartbeat_sound, 0, 0, 0, 50)) else heartbeat++ @@ -114,19 +221,23 @@ if(owner.species && owner.species.flags & NO_BLOOD) return - if(!owner || owner.stat == DEAD || owner.bodytemperature < 170 || owner.InStasis()) //Dead or cryosleep people do not pump the blood. + if(!owner || owner.stat == DEAD || owner.bodytemperature < minimum_temperature_to_pump_blood || owner.InStasis()) //Dead or cryosleep people do not pump the blood. return if(pulse != PULSE_NONE || BP_IS_ROBOTIC(src)) var/blood_volume = round(REAGENT_VOLUME(owner.vessel, /singleton/reagent/blood)) + var/cut_bloodloss_modifier = base_cut_bloodloss_modifier + var/arterial_bloodloss_modifier = base_arterial_bloodloss_modifier + + SEND_SIGNAL(owner, COMSIG_HEART_BLEED_EVENT, &blood_volume, &cut_bloodloss_modifier, &arterial_bloodloss_modifier) //Blood regeneration if there is some space if(blood_volume < species.blood_volume && blood_volume) if(REAGENT_DATA(owner.vessel, /singleton/reagent/blood)) // Make sure there's blood at all - owner.vessel.add_reagent(/singleton/reagent/blood, BLOOD_REGEN_RATE + LAZYACCESS(owner.chem_effects, CE_BLOODRESTORE), temperature = species?.body_temperature) + owner.vessel.add_reagent(/singleton/reagent/blood, (BLOOD_REGEN_RATE * blood_regen_modifier) + LAZYACCESS(owner.chem_effects, CE_BLOODRESTORE), temperature = species?.body_temperature) if(blood_volume <= BLOOD_VOLUME_SAFE) //We lose nutrition and hydration very slowly if our blood is too low - owner.adjustNutritionLoss(2) - owner.adjustHydrationLoss(1) + owner.adjustNutritionLoss(nutrition_cost_per_blood_regen) + owner.adjustHydrationLoss(hydration_cost_per_blood_regen) //Bleeding out var/blood_max = 0 @@ -142,9 +253,9 @@ var/mob/living/carbon/human/H = temp.applied_pressure H.bloody_hands(src, 0) var/min_eff_damage = max(0, W.damage - 10) / 6 - blood_max += max(min_eff_damage, W.damage - 30) / 40 + blood_max += max(min_eff_damage, W.damage - 30) / 40 * cut_bloodloss_modifier else - blood_max += ((W.damage / 40) * species.bleed_mod) + blood_max += ((W.damage / 40) * species.bleed_mod * cut_bloodloss_modifier) if(temp.status & ORGAN_ARTERY_CUT) var/bleed_amount = FLOOR((owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 450 : 250)) * temp.arterial_bleed_severity, 0.1) @@ -157,15 +268,19 @@ blood_max += bleed_amount do_spray += "[temp.name]" else - owner.vessel.remove_reagent(/singleton/reagent/blood, bleed_amount) + owner.vessel.remove_reagent(/singleton/reagent/blood, bleed_amount * arterial_bloodloss_modifier) switch(pulse) if(PULSE_SLOW) - blood_max *= 0.8 + blood_max *= slow_bleeding_modifier if(PULSE_FAST) - blood_max *= 1.25 - if(PULSE_2FAST, PULSE_THREADY) - blood_max *= 1.5 + blood_max *= fast_bleeding_modifier + if(PULSE_2FAST) + blood_max *= too_fast_bleeding_modifier + if (PULSE_THREADY) + blood_max *= thready_bleeding_modifier + + blood_max *= bleed_drip_modifier if(CE_BLOODCLOT in owner.chem_effects) blood_max *= 0.7 @@ -177,11 +292,11 @@ SPAN_DANGER("Blood sprays out of your [pick(do_spray)]!")) owner.eye_blurry = 2 owner.Stun(1) - next_blood_squirt = world.time + 100 + next_blood_squirt = world.time + time_between_arterial_sprays var/turf/sprayloc = get_turf(owner) blood_max -= owner.drip(Ceiling(blood_max/3), sprayloc) if(blood_max > 0) - blood_max -= owner.blood_squirt(blood_max, sprayloc) + blood_max -= owner.blood_squirt(blood_max, sprayloc, blood_spray_distance) if(blood_max > 0) owner.drip(blood_max, get_turf(owner)) else @@ -197,16 +312,22 @@ var/obj/item/organ/external/E = owner.organs_by_name[parent_organ] if(prob(surge_damage)) owner.custom_pain(SPAN_DANGER("Your [E.name] stings horribly!"), 15, FALSE, E) - sound_to(owner, sound('sound/effects/singlebeat.ogg', 0, 0, 0, 100)) + sound_to(owner, sound(heartbeat_sound, 0, 0, 0, 100)) /obj/item/organ/internal/heart/listen() - if(BP_IS_ROBOTIC(src) && is_working()) + if((BP_IS_ROBOTIC(src) && is_working()) && !fake_pulse) if(is_bruised()) return "sputtering pump" else return "steady whirr of the pump" - if(!pulse || (owner.status_flags & FAKEDEATH)) + if (owner.status_flags & FAKEDEATH) + return "no pulse" + + if (fake_pulse) + return "normal pulse" + + if(!pulse) return "no pulse" var/pulsesound = "normal" @@ -224,3 +345,41 @@ pulsesound = "extremely fast and faint" . = "[pulsesound] pulse" + +// Example heart item that has significantly higher statistics. +// Also to be used for the Galatean Bio-augments PRs. +// TODO: After refactoring the organ selector, make it so that this is a selectable heart type(For Galateans) +/obj/item/organ/internal/heart/boosted_heart + name = "boosted heart" + desc = "Intended for athletes, some workers, and soldiers, this improved heart increases blood flow and circulation." \ + + "It provides an improvement to blood oxygenation and stamina, at the cost of requiring more food and water." \ + + "Outside of Galatea, this augment is popular among professional athletes." + icon = 'icons/obj/organs/bioaugs.dmi' + icon_state = "boosted_heart" + max_damage = 80 + min_broken_damage = 60 + shock_stage_for_fibrillation = 140 + fibrillation_stop_risk = 3.5 + base_pump_rate = 1.15 + nutrition_cost_per_blood_regen = 4 + hydration_cost_per_blood_regen = 3 + blood_regen_modifier = 1.1 + bleed_drip_modifier = 1.1 + blood_spray_distance = 3 + +// Example heart item that has significantly lowered statistics. +// TODO: After refactoring the organ selector, make it so that this is a selectable heart type. +/obj/item/organ/internal/heart/scarred_heart + name = "scarred heart" + desc = "Life has not been good to this old ticker." + icon = 'icons/obj/organs/bioaugs.dmi' + icon_state = "scarred_heart" + max_damage = 45 + min_broken_damage = 30 + fibrillation_stop_risk = 7.5 + shock_stage_for_fibrillation = 100 + blood_regen_modifier = 0.9 + base_pump_rate = 0.9 + thready_pump_modifier = 1.5 + damage_from_chemicals = 0.7 + blood_spray_distance = 1 diff --git a/code/modules/organs/internal/liver.dm b/code/modules/organs/internal/liver.dm index 454e9b7b45a..1dda7cfd003 100644 --- a/code/modules/organs/internal/liver.dm +++ b/code/modules/organs/internal/liver.dm @@ -12,69 +12,161 @@ max_damage = 70 relative_size = 60 -/obj/item/organ/internal/liver/process() + /// The base "efficiency" of a liver. + var/base_filter_strength = 1 + + /// The base "Filter effect" rating of a liver, used for calculating whether or not drinking alcohol causes lethal damage. + var/base_filter_effect = 3 + + /// How much dylovene contributes to "filter effect". + var/filter_effect_from_dylovene = 1 + + /** + * How much toxin damage per second a liver-haver takes if they combine alcohol with a non-functional liver. + * This gets multiplied by the inverse absolute average deviation from base filter effect. + */ + var/lethal_booze_dps = 1 + + /// How much a liver being in its "bruised" state modifies its efficiency. + var/intox_filter_bruised = 0.462 + + /// How much a liver being in its "broken" state modifies its efficiency. + var/intox_filter_broken = 0.198 + + /// How much a liver being robotic modifies its efficiency. TODO: Remove this after reworking the organ selector to make robotic organs actually different entities. + var/intox_filter_robotic = 1.1 + + /// The base rate at which this liver processes Dylovene into "Toxin healing" per second. + var/base_toxin_healing_rate = 6 + + /// The upper limit of how much toxin a liver can contain before its efficiency tanks dramatically. + var/toxin_critical_mass = 60 + + /// How much toxin volume above the toxin critical mass contributes to the filter effect. + var/filter_effect_from_critical_toxin = 2 + + /// Modifier on how efficiently this liver eliminates booze. + var/booze_filtering_modifier = 1 + + /// How much the liver being bruised contributes to filter effect. + var/filter_effect_from_bruise = 1 + + /// How much the liver being broken contributes to filter effect. + var/filter_effect_from_broken = 2 + + /// Modifier on how efficiently this liver eliminates booze while blackout drunk. + var/blackout_booze_filtering_modifier = 0.5 + + /// Message to play in chat to a liver-haver when they have an infection. + var/infection_level_one_warning = "Your skin itches." + + /// A largely undamaged healthy liver will gradually heal itself over time by this amount per second + var/liver_regeneration_normal = 6 + + /// A "bruised"(lightly damaged) liver will gradually heal itself by this amount per second. + var/liver_regeneration_bruised = 4 + + /// A liver that is "broken" will heal itself by this amount per second. By default, this is no regeneration. + var/liver_regeneration_broken = 0 + +/obj/item/organ/internal/liver/process(seconds_per_tick) ..() if(!owner) return - if (germ_level > INFECTION_LEVEL_ONE) - owner.notify_message(SPAN_WARNING("Your skin itches."), 2 MINUTES) - if (germ_level > INFECTION_LEVEL_TWO) - if(prob(1)) - spawn owner.delayed_vomit() - - //Detox can heal small amounts of damage - if (damage < max_damage && !owner.chem_effects[CE_TOXIN]) - heal_damage(0.3 * owner.chem_effects[CE_ANTITOXIN]) + if(germ_level > INFECTION_LEVEL_ONE) + owner.notify_message(SPAN_WARNING(infection_level_one_warning), 2 MINUTES) + if(germ_level > INFECTION_LEVEL_TWO && prob(1)) + spawn owner.delayed_vomit() // Get the effectiveness of the liver. - var/filter_effect = 3 + var/filter_effect = base_filter_effect + + // Copy the base filter variables, so that we aren't modifying the original values. + var/filter_strength = base_filter_strength + var/toxin_healing_rate = base_toxin_healing_rate + + // Optionally allow signal repliers to cancel liver filtration. Antifreeze could be a fun one. + var/canceled = FALSE + + // Check if any components on the user wish to mess with liver filtration. + SEND_SIGNAL(owner, COMSIG_LIVER_FILTER_EVENT, &filter_effect, &filter_strength, &toxin_healing_rate, &canceled) + if(canceled) + return + + //Detox can heal small amounts of damage + if(damage < max_damage && !owner.chem_effects[CE_TOXIN]) + heal_damage(toxin_healing_rate * seconds_per_tick * owner.chem_effects[CE_ANTITOXIN]) + if(is_bruised()) - filter_effect -= 1 + filter_strength *= intox_filter_bruised // Defaulted to 0.462 + filter_effect -= filter_effect_from_bruise if(is_broken()) - filter_effect -= 2 - // Robotic organs filter better but don't get benefits from dylovene for filtering. + filter_strength *= intox_filter_broken // Defaulted to 0.198 + filter_effect -= filter_effect_from_broken if(BP_IS_ROBOTIC(src)) - filter_effect += 1 - if(getToxLoss() >= 60) //Too many toxins to process. Abort, abort. - filter_effect -= 2 - else if(owner.chem_effects[CE_ANTITOXIN]) - filter_effect += 1 + filter_strength *= intox_filter_robotic // Defaulted to 1.1 - if(filter_effect < 2) //Trouble. You're not filtering well. - if(owner.intoxication > 0) - owner.adjustToxLoss(0.5 * max(2 - filter_effect, 0)) - - var/filter_strength = INTOX_FILTER_HEALTHY - if(is_bruised()) - filter_strength = INTOX_FILTER_BRUISED - if(is_broken()) - filter_strength = INTOX_FILTER_DAMAGED - if(BP_IS_ROBOTIC(src)) - filter_strength *= 1.1 - - if (owner.intoxication > 0) - var/bac = owner.get_blood_alcohol() - var/res = owner.species ? owner.species.ethanol_resistance : 1 - if(bac >= INTOX_BLACKOUT * res) //Excessive blood alcohol, difficult to filter - owner.intoxication -= min(owner.intoxication, filter_strength/2) - else - owner.intoxication -= min(owner.intoxication, filter_strength) - if(!owner.intoxication) - owner.handle_intoxication() + if(owner.intoxication > 0) + handle_alcohol_poisoning(filter_effect, filter_strength, seconds_per_tick) + if(!is_damaged()) + return if(toxin_type in owner.chem_effects) - if(is_damaged()) - owner.adjustToxLoss(owner.chem_effects[toxin_type] * 0.1 * PROCESS_ACCURACY) // as actual organ damage is handled elsewhere + // Hepatoxic(liver damaging) chems start dealing lethal damage to the patient once the liver is broken. + owner.adjustToxLoss(max(0, (owner.chem_effects[toxin_type] - filter_strength)) * seconds_per_tick) // as actual organ damage is handled elsewhere -/obj/item/organ/internal/liver/handle_regeneration() - if(..()) - if(!owner.total_radiation && damage > 0 && !(owner.get_blood_alcohol() > INTOX_BLACKOUT)) - if(damage < min_broken_damage) - heal_damage(0.2) - if(damage < min_bruised_damage) - heal_damage(0.3) - return TRUE +/// Liver damage + alcohol = bad time. +/obj/item/organ/internal/liver/proc/handle_alcohol_poisoning(var/filter_effect, var/filter_strength, var/seconds_per_tick) + // Robotic organs filter better but don't get benefits from dylovene for filtering. + if(BP_IS_ROBOTIC(src)) // TODO: Just make robot livers have a base filter_effect of 4. + filter_effect += 1 + else if(owner.chem_effects[CE_ANTITOXIN]) + filter_effect += filter_effect_from_dylovene + if(getToxLoss() >= toxin_critical_mass) //Too many toxins to process. Abort, abort. + filter_effect -= filter_effect_from_critical_toxin + if(filter_effect < base_filter_effect) + // Take the inverse absolute average deviation from the base filter effect. + var/booze_damage_factor = base_filter_effect / abs(filter_effect - base_filter_effect) + + // And then multiply it by our tickrate and desired DPS, to get toxin damage every tick. + // Previously this was hardcoded to always fail at 0 or less filter effect, now it arbitrarily doesn't matter if filter effect goes into negatives. + // You now take further scaling damage the lower you go into negatives. This also is no longer limited to integers. + owner.adjustToxLoss(lethal_booze_dps * booze_damage_factor * seconds_per_tick) + + var/bac = owner.get_blood_alcohol() + var/res = owner.species ? owner.species.ethanol_resistance : 1 + var/blackout_effects = (bac >= INTOX_BLACKOUT * res) ? blackout_booze_filtering_modifier : booze_filtering_modifier + + owner.intoxication -= min(owner.intoxication, filter_strength * filter_effect * blackout_effects) + + if(!owner.intoxication) + owner.handle_intoxication() + +/obj/item/organ/internal/liver/handle_regeneration(seconds_per_tick) + if(!..() || owner.total_radiation || damage <= 0 || (owner.get_blood_alcohol() > INTOX_BLACKOUT)) + return FALSE + // This can't be a switch statement because these aren't constants. I tried. + if(liver_regeneration_normal && damage < min_bruised_damage) + heal_damage(liver_regeneration_normal * seconds_per_tick) + return TRUE + if(liver_regeneration_bruised && damage < min_broken_damage) + heal_damage(liver_regeneration_bruised * seconds_per_tick) + return TRUE + if(liver_regeneration_broken) // A default "standard" liver returns false here, since it has a regeneration rate of 0 when broken. + heal_damage(liver_regeneration_broken * seconds_per_tick) + return TRUE return FALSE + +/obj/item/organ/internal/liver/boosted_liver + name = "boosted liver" + desc = "Designed primarily for diplomats or Galateans abroad, the boosted liver improves toxin filtering, giving a resistance to toxin damage. As a consequence, it makes it impossible for the user to get drunk." + //icon = 'icons/obj/organs/bioaugs.dmi' + //icon_state = "boosted_liver" + base_filter_strength = 1.5 + base_filter_effect = 5 + toxin_critical_mass = 90 + booze_filtering_modifier = 100 // "Impossible to get drunk", this should make it impossible. :) + blackout_booze_filtering_modifier = 1 diff --git a/code/modules/organs/subtypes/augment/augment.dm b/code/modules/organs/subtypes/augment/augment.dm index d335e5c432d..c4b670d4b5e 100644 --- a/code/modules/organs/subtypes/augment/augment.dm +++ b/code/modules/organs/subtypes/augment/augment.dm @@ -36,7 +36,8 @@ var/supports_limb = FALSE /obj/item/organ/internal/augment/Initialize() - robotize() + if(robotic == ROBOTIC_MECHANICAL) + robotize() . = ..() /obj/item/organ/internal/augment/refresh_action_button() @@ -82,3 +83,13 @@ /obj/item/organ/internal/augment/proc/do_bruised_act() spark(get_turf(owner), 3) return FALSE + +/obj/item/organ/internal/augment/bioaug + name = "bioaugment" + icon = 'icons/obj/organs/bioaugs.dmi' + icon_state = "boosted_heart" + robotic = FALSE + species_restricted = list( + SPECIES_HUMAN_OFFWORLD, + SPECIES_HUMAN, + ) diff --git a/code/modules/organs/subtypes/augment/augments/auxiliary_heart.dm b/code/modules/organs/subtypes/augment/augments/auxiliary_heart.dm new file mode 100644 index 00000000000..32c386b2763 --- /dev/null +++ b/code/modules/organs/subtypes/augment/augments/auxiliary_heart.dm @@ -0,0 +1,34 @@ +/obj/item/organ/internal/augment/bioaug/auxiliary_heart + name = "auxiliary heart" + desc = "Intended for soldiers and the elderly, the auxiliary heart is a small secondary heart implanted below the original." \ + + "Should the original heart shut down, the secondary heart will activate to help keep the user alive until the original can be restarted." \ + + "This doesn't work if the original heart is completely destroyed, as there needs to be a reasonably intact cardiovascular system." + icon_state = "auxiliary_heart" + organ_tag = BP_AUG_AUX_HEART + parent_organ = BP_CHEST + +/obj/item/organ/internal/augment/bioaug/auxiliary_heart/Initialize() + . = ..() + if(!owner) + return + + RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stabilize_circulation)) + +/obj/item/organ/internal/augment/bioaug/auxiliary_heart/replaced() + . = ..() + if(!owner) + return + + RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stabilize_circulation)) + +/obj/item/organ/internal/augment/bioaug/auxiliary_heart/removed() + . = ..() + if(!owner) + return + + UnregisterSignal(owner, COMSIG_HEART_PUMP_EVENT) + +/obj/item/organ/internal/augment/bioaug/auxiliary_heart/proc/stabilize_circulation(var/obj/item/organ/internal/heart/heart, var/blood_volume, var/recent_pump, var/pulse_mod, var/min_efficiency) + SIGNAL_HANDLER + *min_efficiency *= 1.5 + *blood_volume += 2.5 // This doesn't affect actual blood volume, only the "effective" volume used for calculating thresholds. diff --git a/code/modules/organs/subtypes/augment/augments/fluff.dm b/code/modules/organs/subtypes/augment/augments/fluff.dm index 2f597a0e517..4e73b60dffb 100644 --- a/code/modules/organs/subtypes/augment/augments/fluff.dm +++ b/code/modules/organs/subtypes/augment/augments/fluff.dm @@ -35,3 +35,22 @@ desc = "An augment installed inside the left hand of someone." parent_organ = BP_L_HAND +/obj/item/organ/internal/augment/bioaug/head_fluff + name = "head bioaug" + desc = "A bioaug installed inside the head of someone." + parent_organ = BP_HEAD + +/obj/item/organ/internal/augment/bioaug/head_fluff/chest_fluff + name = "chest bioaug" + desc = "A bioaug installed inside the chest of someone." + parent_organ = BP_CHEST + +/obj/item/organ/internal/augment/bioaug/head_fluff/rhand_fluff + name = "right hand bioaug" + desc = "A bioaug installed inside the right hand of someone." + parent_organ = BP_R_HAND + +/obj/item/organ/internal/augment/bioaug/head_fluff/lhand_fluff + name = "left hand bioaug" + desc = "A bioaug installed inside the left hand of someone." + parent_organ = BP_L_HAND diff --git a/code/modules/organs/subtypes/augment/augments/mind_blanker.dm b/code/modules/organs/subtypes/augment/augments/mind_blanker.dm index 8c185962c24..627881c1217 100644 --- a/code/modules/organs/subtypes/augment/augments/mind_blanker.dm +++ b/code/modules/organs/subtypes/augment/augments/mind_blanker.dm @@ -1,59 +1,70 @@ -/obj/item/organ/internal/augment/mind_blanker +/obj/item/organ/internal/augment/bioaug/mind_blanker name = "mind blanker" desc = "A small, discrete organ attached near the base of the brainstem." \ + "Any attempt to read the mind of an individual with this augment installed will fail, as will attempts at psychic brainwashing." + icon_state = "mind_blanker" organ_tag = BP_AUG_MIND_BLANKER parent_organ = BP_HEAD - robotic = 0 -/obj/item/organ/internal/augment/mind_blanker/Initialize() +/obj/item/organ/internal/augment/bioaug/mind_blanker/Initialize() . = ..() - if(owner) - RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power)) + if(!owner) + return -/obj/item/organ/internal/augment/mind_blanker/replaced() + RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power)) + +/obj/item/organ/internal/augment/bioaug/mind_blanker/replaced() . = ..() - if(owner) - RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power)) + if(!owner) + return -/obj/item/organ/internal/augment/mind_blanker/removed() + RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power)) + +/obj/item/organ/internal/augment/bioaug/mind_blanker/removed() . = ..() - if(owner) - UnregisterSignal(owner, COMSIG_PSI_MIND_POWER) + if(!owner) + return -/obj/item/organ/internal/augment/mind_blanker/proc/cancel_power(mob/caster) + UnregisterSignal(owner, COMSIG_PSI_MIND_POWER) + +/obj/item/organ/internal/augment/bioaug/mind_blanker/proc/cancel_power(mob/caster, var/cancelled) SIGNAL_HANDLER - return COMSIG_PSI_MIND_POWER_CANCELLED + *cancelled = TRUE -/obj/item/organ/internal/augment/mind_blanker_lethal +/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal name = "lethal mind blanker" desc = "A small, discrete organ attached near the base of the brainstem." \ + "Available only to higher-up MfAS agents and members of the Galatean government." \ + "This enhanced variant of a mind blanker includes a psionic trap which inflicts severe neural damage on anyone attempting to read the user's mind." organ_tag = BP_AUG_MIND_BLANKER_L parent_organ = BP_HEAD - robotic = 0 -/obj/item/organ/internal/augment/mind_blanker_lethal/Initialize() +/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/Initialize() . = ..() - if(owner) - RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal)) + if(!owner) + return -/obj/item/organ/internal/augment/mind_blanker_lethal/replaced() + RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal)) + +/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/replaced() . = ..() - if(owner) - RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal)) + if(!owner) + return -/obj/item/organ/internal/augment/mind_blanker_lethal/removed() + RegisterSignal(owner, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power_lethal)) + +/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/removed() . = ..() - if(owner) - UnregisterSignal(owner, COMSIG_PSI_MIND_POWER) + if(!owner) + return -/obj/item/organ/internal/augment/mind_blanker_lethal/proc/cancel_power_lethal(mob/caster) + UnregisterSignal(owner, COMSIG_PSI_MIND_POWER) + +/obj/item/organ/internal/augment/bioaug/mind_blanker_lethal/proc/cancel_power_lethal(mob/caster, var/cancelled) SIGNAL_HANDLER + *cancelled = TRUE if(isliving(caster)) var/mob/living/victim = caster victim.adjustBrainLoss(20) victim.confused += 20 to_chat(victim, SPAN_DANGER("Agony lances through my mind as [src]'s mind clamps down upon me!")) - return COMSIG_PSI_MIND_POWER_CANCELLED diff --git a/code/modules/organs/subtypes/augment/augments/platelet_factories.dm b/code/modules/organs/subtypes/augment/augments/platelet_factories.dm new file mode 100644 index 00000000000..a4b69955fc7 --- /dev/null +++ b/code/modules/organs/subtypes/augment/augments/platelet_factories.dm @@ -0,0 +1,42 @@ +/obj/item/organ/internal/augment/bioaug/platelet_factories + name = "platelet factories" + desc = "Designed for military applications, this implant massively increases the user's blood clotting factor." \ + + "This provides an extreme resistance to arterial bleeds, effectively all but preventing exsanguination." \ + + "This augment has a reputation for causing heart attacks and strokes at a high rate, and is usually combined with an auxiliary heart for safety." + icon_state = "platelet_factories" + organ_tag = BP_AUG_PLATELET_FACTORIES + parent_organ = BP_CHEST + +/obj/item/organ/internal/augment/bioaug/platelet_factories/Initialize() + . = ..() + if(!owner) + return + + RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stroke_risk)) + RegisterSignal(owner, COMSIG_HEART_BLEED_EVENT, PROC_REF(reduce_bloodloss)) + +/obj/item/organ/internal/augment/bioaug/platelet_factories/replaced() + . = ..() + if(!owner) + return + + RegisterSignal(owner, COMSIG_HEART_PUMP_EVENT, PROC_REF(stroke_risk)) + RegisterSignal(owner, COMSIG_HEART_BLEED_EVENT, PROC_REF(reduce_bloodloss)) + +/obj/item/organ/internal/augment/bioaug/platelet_factories/removed() + . = ..() + if(!owner) + return + + UnregisterSignal(owner, COMSIG_HEART_PUMP_EVENT) + UnregisterSignal(owner, COMSIG_HEART_BLEED_EVENT) + +/obj/item/organ/internal/augment/bioaug/platelet_factories/proc/stroke_risk(var/obj/item/organ/internal/heart/heart, var/blood_volume, var/recent_pump, var/pulse_mod, var/min_efficiency) + SIGNAL_HANDLER + *min_efficiency *= 0.85 + *pulse_mod *= 0.95 + +/obj/item/organ/internal/augment/bioaug/platelet_factories/proc/reduce_bloodloss(var/blood_volume, var/cut_bloodloss_modifier, var/arterial_bloodloss_modifier) + SIGNAL_HANDLER + *cut_bloodloss_modifier *= 0.1 + *arterial_bloodloss_modifier *= 0.25 diff --git a/code/modules/organs/subtypes/augment/augments/subdermal_carapace.dm b/code/modules/organs/subtypes/augment/augments/subdermal_carapace.dm new file mode 100644 index 00000000000..cdf2f235f44 --- /dev/null +++ b/code/modules/organs/subtypes/augment/augments/subdermal_carapace.dm @@ -0,0 +1,30 @@ +/obj/item/organ/internal/augment/bioaug/subdermal_carapace + name = "subdermal carapace" + desc = "Used by the Galatean military to provide additional ballistic protection. This augment causes accelerated bone growth in the ribcage," \ + + "transforming it into a solid, hardened plate of bone. This provides a small amount of protection to vital organs." \ + + "In combination with armor, it can turn lethal injuries into merely serious wounds." + icon_state = "subdermal_carapace" + organ_tag = BP_AUG_SUBDERMAL_CARAPACE + parent_organ = BP_CHEST + +/obj/item/organ/internal/augment/bioaug/subdermal_carapace/Initialize() + . = ..() + if(!owner) + return + + owner.AddComponent(/datum/component/armor, list(MELEE = ARMOR_MELEE_SMALL, BULLET = ARMOR_BALLISTIC_MINOR)) + +/obj/item/organ/internal/augment/bioaug/subdermal_carapace/replaced() + . = ..() + if(!owner) + return + + owner.AddComponent(/datum/component/armor, list(MELEE = ARMOR_MELEE_SMALL, BULLET = ARMOR_BALLISTIC_MINOR)) + +/obj/item/organ/internal/augment/bioaug/subdermal_carapace/removed() + . = ..() + if(!owner) + return + + var/datum/component/armor/armor_component = owner.GetComponent(/datum/component/armor) + qdel(armor_component) diff --git a/code/modules/psionics/mob/mob_helpers.dm b/code/modules/psionics/mob/mob_helpers.dm index 7c47547db97..00ab36e535a 100644 --- a/code/modules/psionics/mob/mob_helpers.dm +++ b/code/modules/psionics/mob/mob_helpers.dm @@ -9,8 +9,9 @@ return !has_psionics() /mob/living/carbon/is_psi_blocked(mob/user) - var/cancelled = SEND_SIGNAL(src, COMSIG_PSI_MIND_POWER, user) - if((cancelled & COMSIG_PSI_MIND_POWER_CANCELLED) && !has_zona_bovinae() && !has_psi_aug()) + var/cancelled = FALSE + SEND_SIGNAL(src, COMSIG_PSI_MIND_POWER, user, &cancelled) + if(cancelled || (!has_zona_bovinae() && !has_psi_aug())) return SPAN_WARNING("[src]'s mind is inaccessible, like hitting a brick wall.") for (var/obj/item/implant/mindshield/I in src) diff --git a/html/changelogs/hellfirejag-heartrefactor.yml b/html/changelogs/hellfirejag-heartrefactor.yml new file mode 100644 index 00000000000..aa6ceae6446 --- /dev/null +++ b/html/changelogs/hellfirejag-heartrefactor.yml @@ -0,0 +1,7 @@ +author: Hellfirejag +delete-after: True +changes: + - refactor: "Gently refactored organ code in preparation for Galatean BioAugs." + - rscadd: "Added Auxiliary Heart as a new loadout implant available to Galateans" + - rscadd: "Added Platelet Factories and Subdermal Carapace as new loadout implants available only to Galatean Consulars." + - rscadd: "Auxiliary Heart, Mind Blankers (both types), Platelet Factories, and Subdermal Carapace are now available to antagonists via their uplink." diff --git a/icons/obj/organs/bioaugs.dmi b/icons/obj/organs/bioaugs.dmi new file mode 100644 index 0000000000000000000000000000000000000000..0adf8115965fae941526e977fc6760d61064154f GIT binary patch literal 2120 zcmZ`*cTm$=5dHxI5eQ8bLXC1NMFY}{KvY_!3r9J+N^gM#h-gd*MQMr@L4`w#90&yI z3ZV!hNbfC(6g5DkLx2F`a&v#)+{}A#XJ>Zz+x_16$HrNj8-ck*xc~qFBaHQLF)f~X z-9Q}7cQhgb!89zWTSnIbVxRaD(;-FJ*rWA5PX!(Hn_PeB*6hEtb_r(zCD`#IiHXUr?+MMNh+lxy-Z8&X;rKl5y?^iem zX*7tPMmudLDkuHSYa{IeEYcUUm(oiwZ`&0_D|>WyV7eVl2IhL!Dy95lMk3R}{5%7* zc}f$YGg1DzC!g@p4cI?Fs^IWW5shr!eJE*Nt!U6=8>}0#lEqiVNX1@Fe3)i!Wz#Xf zl~bS;66w4gUu!L0PpY5$j>{YWZeO{_*=;gL02+ z+>Igi=Ev)t`E7AH|3wBQR>(d6z^mOnYvd1q$dfs~u{q0Xi%T$+%@ks=Hg3r7Rl7O) zgh@>4I?X5~N5OBjaW~LK@V>4k28;N%JA>-e*_|=!X9p3WAOZ$LM)`+SlHr*=C@oE# zq>^h^&=oqqZ&_~ADl_4CJv8K@?b}M7jtk+Yu?uuc0J5Fku%4zD)p4VX`gV@}J6e@s zO9I0jkX)FmFo{B}ZuFz`=b=Nr98RKS2j}y+!(PhX5qa3h3xCZ0ii;NLl(bpz`-71; ze;cr5%{}mys@<736%!+Uq+T1=l`1!S^P5DfyGeip(nJ{tl z#7Jp*rm9YXl2aj2wXXL5`%HtNTn6`IqgZIG7Z&$YYw8~Ww%%+2KUJv&zb z3|M8B+y%xi)JTZOe2DW-A;`pIQd|`h@<3KXp?XGkfFgz4oaIO8XmV_UgZbRGmPRAm zQ*$WUHzr?E_^aK)ysmH-Yh~G3w70a{T1k|fW4~EE`9$kp1VL zU)kW{9ByRjr`kw@^{nJhPlYN~UMNb73zhCi+s`LIthOmlZUn0aS7q&UHgg{kPPdBK?!8N$e$<{nERqs^z7MrGoxp`jy(_H)6``n#{O^q@A z!jT4QV!qv7Ert5Z_b4S;AcRNf28zSY5X5H^yp@eZ6ArkOk3Kp+Z-;!`gM(n7(8%1) z>|Ep-+wt%6%?Cyo(*htC_RXV0ll~$nA4N{2J)Wpe138i&6MGOhaOJXWXj{84J@1#T zayt7~BV-(&Os#~d@c#mUot>fiN$2F)-3ixr{UdBuajMuX%+X*Q8iy%!bo%?f>>kZ^_*H@qlJr}!>knKv&U zy;>?@zh3epyf(8NbmAKS|DB}-nRsMQlXQtZYBE%7`L9tHf#TLiYSel(eeTKV_j}im ziY%!4LB-&pySDCXC_udTC0Eyl&yiDIUOJgQQvP=w2M~#ztr@Gx74gm9Z8@dc}0-7pe7x8@tnAY-jvg zhQv2s)$& zfuO0Wy>Q8**Dv~U;mfpfDbHAUHWJy*9`R!o1=YEXUBzB1w^XXWL6kkUOJx=_`3AeL zWC$I`HE&-Y-f`4k>z*CK^|H*Kl#C=ZJZ@1J8k5X6AswoRja#RLJo;X1D|x;>H9}7; z4_A9BdV)kRmj(K2vEGy6>*~`Dx+3Ayn%);WCYN{OBF;LeNM^K6Z`_V`pE;# zBA2wR3A2d{qD^p#3MNOQ=i|MORn;|4FAr0E*_MX)f&a*| b=cnubJ!^@TvJ6k=HUJ