From 6a4d1213c1d0c5101709c3cc245fd64656b48408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D1=80=D0=BA?= Date: Tue, 12 Nov 2024 17:11:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D1=80=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=A0=D0=B5=D0=B0=D0=B3=D0=B5=D0=BD=D1=82.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/antagonists.dm | 7 +- code/modules/mob/living/damage_procs.dm | 23 +++ code/modules/mob/living/living.dm | 30 ++- .../chemistry/reagents/medicine_reagents.dm | 183 ++++++++++++------ 4 files changed, 177 insertions(+), 66 deletions(-) diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index cd94c36316be..11144963e16d 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -71,9 +71,12 @@ ///Heretics -- GLOBAL_LIST_EMPTY(living_heart_cache) //A list of all living hearts in existance, for us to iterate through. -#define IS_INTEQ(mob) (mob.mind?.has_antag_datum(/datum/antagonist/traitor) || mob.mind?.has_antag_datum(/datum/antagonist/raiders) || mob.mind?.has_antag_datum(/datum/antagonist/nukeop) || (ROLE_INTEQ in mob.faction)) +#define IS_INTEQ(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/traitor) || mob.mind?.has_antag_datum(/datum/antagonist/raiders) || mob.mind?.has_antag_datum(/datum/antagonist/nukeop) || (ROLE_INTEQ in mob.faction)) -#define IS_HERETIC(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic)) +/// Checks if the given mob is a changeling +#define IS_CHANGELING(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/changeling)) + +#define IS_HERETIC(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/heretic)) #define IS_HERETIC_MONSTER(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic_monster)) /// Checks if the given mob is a malf ai. diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 7889ad8d70d9..31f45ca94fec 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -151,6 +151,18 @@ updatehealth() return amount +/mob/living/proc/setBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) + if(!forced && (status_flags & GODMODE)) + return FALSE + . = bruteloss + bruteloss = amount + + if(!.) // no change, no need to update + return FALSE + if(updating_health) + updatehealth() + . -= bruteloss + /mob/living/proc/getOxyLoss() return oxyloss @@ -226,6 +238,17 @@ updatehealth() return amount +/mob/living/proc/setFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype = ALL) + if(!forced && (status_flags & GODMODE)) + return 0 + . = fireloss + fireloss = amount + . -= fireloss + if(. == 0) // no change, no need to update + return 0 + if(updating_health) + updatehealth() + /mob/living/proc/getCloneLoss() return cloneloss diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 348d48521a82..244e5eec8fd1 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -643,8 +643,12 @@ clear_fullscreen("brute") //Proc used to resuscitate a mob, for full_heal see fully_heal() -/mob/living/proc/revive(full_heal = FALSE, admin_revive = FALSE) +/mob/living/proc/revive(full_heal = FALSE, admin_revive = FALSE, excess_healing = 0) SEND_SIGNAL(src, COMSIG_LIVING_REVIVE, full_heal, admin_revive) + if(excess_healing) + adjustOxyLoss(-excess_healing, updating_health = FALSE) + adjustToxLoss(-excess_healing, updating_health = FALSE, forced = TRUE) //slime friendly + updatehealth() if(full_heal) fully_heal(admin_revive) if(stat == DEAD && can_be_revived()) //in some cases you can't revive (e.g. no brain) @@ -659,7 +663,10 @@ update_sight() clear_alert("not_enough_oxy") reload_fullscreen() - . = 1 + . = TRUE + if(excess_healing) + INVOKE_ASYNC(src, PROC_REF(emote), "gasp") + log_combat(src, src, "revived") if(mind) for(var/S in mind.spell_list) var/obj/effect/proc_holder/spell/spell = S @@ -1429,3 +1436,22 @@ STAMINA:[getStaminaLoss()] "} + +/** + * Called by strange_reagent, with the amount of healing the strange reagent is doing + * It uses the healing amount on brute/fire damage, and then uses the excess healing for revive + */ +/mob/living/proc/do_strange_reagent_revival(healing_amount) + var/brute_loss = getBruteLoss() + if(brute_loss) + var/brute_healing = min(healing_amount * 0.5, brute_loss) // 50% of the healing goes to brute + setBruteLoss(round(brute_loss - brute_healing, DAMAGE_PRECISION), updating_health=FALSE, forced=TRUE) + healing_amount = max(0, healing_amount - brute_healing) + + var/fire_loss = getFireLoss() + if(fire_loss && healing_amount) + var/fire_healing = min(healing_amount, fire_loss) // rest of the healing goes to fire + setFireLoss(round(fire_loss - fire_healing, DAMAGE_PRECISION), updating_health=TRUE, forced=TRUE) + healing_amount = max(0, healing_amount - fire_healing) + + revive(FALSE, FALSE, excess_healing=max(healing_amount, 0)) // and any excess healing is passed along diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 8b5891ce6d88..3c554bfaa790 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -995,74 +995,133 @@ description = "A miracle drug capable of bringing the dead back to life. Only functions when applied by patch or spray, if the target has less than 100 brute and burn damage (independent of one another) and hasn't been husked. Causes slight damage to the living." reagent_state = LIQUID color = "#A0E85E" - metabolization_rate = 0.5 * REAGENTS_METABOLISM + metabolization_rate = 1.25 * REAGENTS_METABOLISM // chemical_flags = REAGENT_ALL_PROCESS (BLUEMOON REMOVAL - роботы не должны получать эффекты реагента) taste_description = "magnets" - pH = 0 + pH = 0.5 value = REAGENT_VALUE_RARE + /// The amount of damage a single unit of this will heal + var/healing_per_reagent_unit = 5 + /// The ratio of the excess reagent used to contribute to excess healing + var/excess_healing_ratio = 0.8 + /// Do we instantly revive + var/instant = FALSE + /// The maximum amount of damage we can revive from, as a ratio of max health + var/max_revive_damage_ratio = 2 + +/datum/reagent/medicine/strange_reagent/instant + name = "Stranger Reagent" + instant = TRUE + chemical_flags = NONE + +/datum/reagent/medicine/strange_reagent/New() + . = ..() + description = replacetext(description, "%MAXHEALTHRATIO%", "[max_revive_damage_ratio * 100]%") + if(instant) + description += " It appears to be pulsing with a warm pink light." // FEED ME SEYMOUR -/datum/reagent/medicine/strange_reagent/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) +/datum/reagent/medicine/strange_reagent/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) + mytray.spawnplant() + +/// Calculates the amount of reagent to at a bare minimum make the target not dead +/datum/reagent/medicine/strange_reagent/proc/calculate_amount_needed_to_revive(mob/living/benefactor) + var/their_health = benefactor.getMaxHealth() - (benefactor.getBruteLoss() + benefactor.getFireLoss()) + if(their_health > 0) + return 1 + + return round(-their_health / healing_per_reagent_unit, DAMAGE_PRECISION) + +/// Calculates the amount of reagent that will be needed to both revive and full heal the target. Looks at healing_per_reagent_unit and excess_healing_ratio +/datum/reagent/medicine/strange_reagent/proc/calculate_amount_needed_to_full_heal(mob/living/benefactor) + var/their_health = benefactor.getBruteLoss() + benefactor.getFireLoss() + var/max_health = benefactor.getMaxHealth() + if(their_health >= max_health) + return 1 + + var/amount_needed_to_revive = calculate_amount_needed_to_revive(benefactor) + var/expected_amount_to_full_heal = round(max_health / healing_per_reagent_unit, DAMAGE_PRECISION) / excess_healing_ratio + return amount_needed_to_revive + expected_amount_to_full_heal + +/datum/reagent/medicine/strange_reagent/reaction_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) + if(exposed_mob.stat != DEAD || !(exposed_mob.mob_biotypes & MOB_ORGANIC)) + return ..() + + if(exposed_mob.suiciding) //they are never coming back + exposed_mob.visible_message(span_warning("Тело [exposed_mob] не реагирует...")) + return + + if(iscarbon(exposed_mob) && !(methods & INGEST)) //simplemobs can still be splashed + return ..() + + if(HAS_TRAIT(exposed_mob, TRAIT_HUSK)) + exposed_mob.visible_message(span_warning("Тело [exposed_mob] выпустило кучу дыма...")) + var/datum/effect_system/smoke_spread/bad/smoke = new + smoke.set_up(8, exposed_mob.loc) + smoke.start() + return + + if((exposed_mob.getBruteLoss() + exposed_mob.getFireLoss()) > (exposed_mob.getMaxHealth() * max_revive_damage_ratio)) + if(IS_CHANGELING(exposed_mob) || iszombie(exposed_mob)) + return + var/num = rand(0, 1) + switch(num) + if(0) + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living, do_jitter_animation), 1 SECONDS), 4 SECONDS) + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living, gib), FALSE, TRUE, TRUE), 2 SECONDS) + if(1) + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living, do_jitter_animation), 1 SECONDS), 4 SECONDS) + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living/carbon/human, cluwneify)), 2 SECONDS) + return + + var/needed_to_revive = calculate_amount_needed_to_revive(exposed_mob) + if(reac_volume < needed_to_revive) + exposed_mob.visible_message(span_warning("Тело [exposed_mob] немного конвульсирует, а затем снова замирает..")) + exposed_mob.do_jitter_animation(10) + return + + exposed_mob.visible_message(span_warning("Тело [exposed_mob] начинает дёргаться!")) + exposed_mob.notify_ghost_cloning(source = exposed_mob) + exposed_mob.do_jitter_animation(10) + + // we factor in healing needed when determing if we do anything + var/healing = needed_to_revive * healing_per_reagent_unit + // but excessive healing is penalized, to reward doctors who use the perfect amount + reac_volume -= needed_to_revive + healing += (reac_volume * healing_per_reagent_unit) * excess_healing_ratio + + // during unit tests, we want it to happen immediately + if(instant) + exposed_mob.do_strange_reagent_revival(healing) + else + // jitter immediately, after four seconds, and after eight seconds + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living, do_jitter_animation), 1 SECONDS), 4 SECONDS) + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living, do_strange_reagent_revival), healing), 7 SECONDS) + addtimer(CALLBACK(exposed_mob, TYPE_PROC_REF(/mob/living, do_jitter_animation), 1 SECONDS), 8 SECONDS) + + var/tplus = world.time - exposed_mob.timeofdeath + if(exposed_mob.revive()) + exposed_mob.grab_ghost() + var/list/policies = CONFIG_GET(keyed_list/policy) + var/timelimit = CONFIG_GET(number/defib_cmd_time_limit) * 10 //the config is in seconds, not deciseconds + var/late = timelimit && (tplus > timelimit) + var/policy = policies[POLICYCONFIG_ON_DEFIB_LATE] //Always causes memory loss due to the nature of strange reagent. + if(policy) + to_chat(exposed_mob, policy) + exposed_mob.log_message("revived using strange reagent, [tplus] deciseconds from time of death, considered late revival due to usage of strange reagent.", LOG_GAME) + message_admins("[ADMIN_LOOKUPFLW(exposed_mob)] возвращён к жизни и [late? "всё помнит" : "ничего не помнит"].") + log_admin(exposed_mob, "[exposed_mob] возвращён к жизни и [late? "всё помнит" : "ничего не помнит"].") + + return ..() + +/datum/reagent/medicine/strange_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(chems.has_reagent(type, 1)) - mytray.spawnplant() - -/datum/reagent/medicine/strange_reagent/reaction_mob(mob/living/M, method=TOUCH, reac_volume) - if(M.stat == DEAD) - if(M.suiciding || M.hellbound) //they are never coming back - M.visible_message("[M]'s body does not react...") - return ..() - if(M.getBruteLoss() >= 100 || M.getFireLoss() >= 100 || HAS_TRAIT(M, TRAIT_HUSK)) //body is too damaged to be revived - M.visible_message("[M]'s body convulses a bit, and then falls still once more.") - M.do_jitter_animation(10) - return ..() - else - M.visible_message("[M]'s body starts convulsing!") - M.notify_ghost_cloning(source = M) - M.do_jitter_animation(10) - addtimer(CALLBACK(M, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 10), 40) //jitter immediately, then again after 4 and 8 seconds - addtimer(CALLBACK(M, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 10), 80) - - spawn(100) //so the ghost has time to re-enter - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!(C.dna && C.dna.species && (NOBLOOD in C.dna.species.species_traits))) - C.blood_volume = max(C.blood_volume, BLOOD_VOLUME_BAD*C.blood_ratio) //so you don't instantly re-die from a lack of blood. You'll still need help if you had none though. - var/obj/item/organ/heart/H = C.getorganslot(ORGAN_SLOT_HEART) - if(H && H.organ_flags & ORGAN_FAILING) - H.applyOrganDamage(-15) - for(var/obj/item/organ/O as anything in C.internal_organs) - if(O.organ_flags & ORGAN_FAILING) - O.applyOrganDamage(-5) - - M.adjustOxyLoss(-20, 0) - M.adjustToxLoss(-20, 0) - M.updatehealth() - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.can_revive(ignore_timelimit = TRUE, maximum_brute_dam = 100, maximum_fire_dam = 100, ignore_heart = TRUE)) - return - var/tplus = world.time - M.timeofdeath - if(M.revive()) - M.grab_ghost() - M.emote("gasp") - log_combat(M, M, "revived", src) - var/list/policies = CONFIG_GET(keyed_list/policy) - var/timelimit = CONFIG_GET(number/defib_cmd_time_limit) * 10 //the config is in seconds, not deciseconds - var/late = timelimit && (tplus > timelimit) - var/policy = policies[POLICYCONFIG_ON_DEFIB_LATE] //Always causes memory loss due to the nature of strange reagent. - if(policy) - to_chat(M, policy) - M.log_message("revived using strange reagent, [tplus] deciseconds from time of death, considered late revival due to usage of strange reagent.", LOG_GAME) - message_admins("[ADMIN_LOOKUPFLW(M)] возвращён к жизни и [late? "всё помнит" : "ничего не помнит"].") - log_admin(M, "[M] возвращён к жизни и [late? "всё помнит" : "ничего не помнит"].") - ..() - - -/datum/reagent/medicine/strange_reagent/on_mob_life(mob/living/carbon/M) - M.adjustBruteLoss(0.5*REM, 0) - M.adjustFireLoss(0.5*REM, 0) - ..() + + var/damage_at_random = rand(0, 250)/100 //0 to 2.5 + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(damage_at_random * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustFireLoss(damage_at_random * REM * seconds_per_tick, updating_health = FALSE) + . = 1 /datum/reagent/medicine/mannitol