diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index bd20ba610c1..398e1bebd4c 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -25,6 +25,7 @@ #define COLOR_VIVID_RED "#FF3232" #define COLOR_LIGHT_GRAYISH_RED "#E4C7C5" #define COLOR_SOFT_RED "#FA8282" +#define COLOR_CULT_RED "#960000" #define COLOR_BUBBLEGUM_RED "#950A0A" #define COLOR_YELLOW "#FFFF00" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 80fdfe933b5..413de14d122 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -768,3 +768,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// radiation processing as if this wasn't already irradiated. /// Basically, without this, COMSIG_IN_RANGE_OF_IRRADIATION won't fire once the object is irradiated. #define TRAIT_BYPASS_EARLY_IRRADIATED_CHECK "radiation_bypass_early_irradiated_check" + +// Traits to heal for + +/// This mob heals from carp rifts. +#define TRAIT_HEALS_FROM_CARP_RIFTS "heals_from_carp_rifts" + +/// This mob heals from cult pylons. +#define TRAIT_HEALS_FROM_CULT_PYLONS "heals_from_cult_pylons" diff --git a/code/controllers/subsystem/processing/aura_healing.dm b/code/controllers/subsystem/processing/aura_healing.dm new file mode 100644 index 00000000000..ad3fa6f4278 --- /dev/null +++ b/code/controllers/subsystem/processing/aura_healing.dm @@ -0,0 +1,5 @@ +/// The subsystem used to tick [/datum/component/aura_healing] instances. +PROCESSING_SUBSYSTEM_DEF(aura_healing) + name = "Aura Healing" + flags = SS_NO_INIT | SS_BACKGROUND | SS_KEEP_TIMING + wait = 0.3 SECONDS diff --git a/code/datums/components/aura_healing.dm b/code/datums/components/aura_healing.dm new file mode 100644 index 00000000000..9127d8e4680 --- /dev/null +++ b/code/datums/components/aura_healing.dm @@ -0,0 +1,147 @@ +#define HEAL_EFFECT_COOLDOWN (1 SECONDS) + +/// Applies healing to those in the area. +/// Will provide them with an alert while they're in range, as well as +/// give them a healing particle. +/// Can be applied to those only with a trait conditionally. +/datum/component/aura_healing + /// The range of which to heal + var/range + + /// Whether or not you must be a visible object of the parent + var/requires_visibility = TRUE + + /// Brute damage to heal over a second + var/brute_heal = 0 + + /// Burn damage to heal over a second + var/burn_heal = 0 + + /// Toxin damage to heal over a second + var/toxin_heal = 0 + + /// Suffocation damage to heal over a second + var/suffocation_heal = 0 + + /// Stamina damage to heal over a second + var/stamina_heal = 0 + + /// Amount of cloning damage to heal over a second + var/clone_heal = 0 + + /// Amount of blood to heal over a second + var/blood_heal = 0 + + /// Map of organ (such as ORGAN_SLOT_BRAIN) to damage heal over a second + var/list/organ_healing = null + + /// Amount of damage to heal on simple mobs over a second + var/simple_heal = 0 + + /// Trait to limit healing to, if set + var/limit_to_trait = null + + /// The color to give the healing visual + var/healing_color = COLOR_GREEN + + /// A list of being healed to active alerts + var/list/current_alerts = list() + + COOLDOWN_DECLARE(last_heal_effect_time) + +/datum/component/aura_healing/Initialize( + range, + requires_visibility = TRUE, + brute_heal = 0, + burn_heal = 0, + toxin_heal = 0, + suffocation_heal = 0, + stamina_heal = 0, + clone_heal = 0, + blood_heal = 0, + organ_healing = null, + simple_heal = 0, + limit_to_trait = null, + healing_color = COLOR_GREEN, +) + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + START_PROCESSING(SSaura_healing, src) + + src.range = range + src.requires_visibility = requires_visibility + src.brute_heal = brute_heal + src.burn_heal = burn_heal + src.toxin_heal = toxin_heal + src.suffocation_heal = suffocation_heal + src.stamina_heal = stamina_heal + src.clone_heal = clone_heal + src.blood_heal = blood_heal + src.organ_healing = organ_healing + src.simple_heal = simple_heal + src.limit_to_trait = limit_to_trait + src.healing_color = healing_color + +/datum/component/aura_healing/Destroy(force, silent) + STOP_PROCESSING(SSaura_healing, src) + + QDEL_LIST_ASSOC_VAL(current_alerts) + + return ..() + +/datum/component/aura_healing/process(delta_time) + var/should_show_effect = COOLDOWN_FINISHED(src, last_heal_effect_time) + if (should_show_effect) + COOLDOWN_START(src, last_heal_effect_time, HEAL_EFFECT_COOLDOWN) + + var/list/remove_alerts_from = current_alerts.Copy() + + var/alert_category = "aura_healing_[REF(src)]" + + for (var/mob/living/candidate in (requires_visibility ? view(range, parent) : range(range, parent))) + if (!isnull(limit_to_trait) && !HAS_TRAIT(candidate, limit_to_trait)) + continue + + remove_alerts_from -= candidate + + if (!(candidate in current_alerts)) + var/atom/movable/screen/alert/aura_healing/alert = candidate.throw_alert(alert_category, /atom/movable/screen/alert/aura_healing, new_master = parent) + alert.desc = "You are being healed by [parent]." + current_alerts[candidate] = alert + + if (should_show_effect && candidate.health < candidate.maxHealth) + new /obj/effect/temp_visual/heal(get_turf(candidate), healing_color) + + if (iscarbon(candidate) || issilicon(candidate)) + candidate.adjustBruteLoss(-brute_heal * delta_time, updating_health = FALSE) + candidate.adjustFireLoss(-burn_heal * delta_time, updating_health = FALSE) + + if (iscarbon(candidate)) + // Toxin healing is forced for slime people + candidate.adjustToxLoss(-toxin_heal * delta_time, updating_health = FALSE, forced = TRUE) + + candidate.adjustOxyLoss(-suffocation_heal * delta_time, updating_health = FALSE) + candidate.adjustStaminaLoss(-stamina_heal * delta_time, updating_health = FALSE) + candidate.adjustCloneLoss(-clone_heal * delta_time, updating_health = FALSE) + + for (var/organ in organ_healing) + candidate.adjustOrganLoss(organ, -organ_healing[organ] * delta_time) + else if (isanimal(candidate)) + var/mob/living/simple_animal/simple_candidate = candidate + simple_candidate.adjustHealth(-simple_heal * delta_time, updating_health = FALSE) + + if (candidate.blood_volume < BLOOD_VOLUME_NORMAL) + candidate.blood_volume += blood_heal * delta_time + + candidate.updatehealth() + + for (var/mob/remove_alert_from as anything in remove_alerts_from) + remove_alert_from.clear_alert(alert_category) + current_alerts -= remove_alert_from + +/atom/movable/screen/alert/aura_healing + name = "Aura Healing" + icon_state = "template" + +#undef HEAL_EFFECT_COOLDOWN diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 3111a40b95c..b667000177d 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -212,10 +212,30 @@ tick_interval = 25 examine_text = "They seem to have an aura of healing and helpfulness about them." alert_type = null + + var/datum/component/aura_healing/aura_healing var/hand var/deathTick = 0 /datum/status_effect/hippocratic_oath/on_apply() + var/static/list/organ_healing = list( + ORGAN_SLOT_BRAIN = 1.4, + ) + + aura_healing = owner.AddComponent( \ + /datum/component/aura_healing, \ + range = 7, \ + brute_heal = 1.4, \ + burn_heal = 1.4, \ + toxin_heal = 1.4, \ + suffocation_heal = 1.4, \ + stamina_heal = 1.4, \ + clone_heal = 0.4, \ + simple_heal = 1.4, \ + organ_healing = organ_healing, \ + healing_color = "#375637", \ + ) + //Makes the user passive, it's in their oath not to harm! ADD_TRAIT(owner, TRAIT_PACIFISM, HIPPOCRATIC_OATH_TRAIT) var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] @@ -223,6 +243,7 @@ return ..() /datum/status_effect/hippocratic_oath/on_remove() + QDEL_NULL(aura_healing) REMOVE_TRAIT(owner, TRAIT_PACIFISM, HIPPOCRATIC_OATH_TRAIT) var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] H.remove_hud_from(owner) @@ -273,24 +294,6 @@ itemUser.adjustStaminaLoss(-1.5) itemUser.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1.5) itemUser.adjustCloneLoss(-0.5) //Becasue apparently clone damage is the bastion of all health - //Heal all those around you, unbiased - for(var/mob/living/L in view(7, owner)) - if(L.health < L.maxHealth) - new /obj/effect/temp_visual/heal(get_turf(L), "#375637") - if(iscarbon(L)) - L.adjustBruteLoss(-3.5) - L.adjustFireLoss(-3.5) - L.adjustToxLoss(-3.5, forced = TRUE) //Because Slime People are people too - L.adjustOxyLoss(-3.5) - L.adjustStaminaLoss(-3.5) - L.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3.5) - L.adjustCloneLoss(-1) //Becasue apparently clone damage is the bastion of all health - else if(issilicon(L)) - L.adjustBruteLoss(-3.5) - L.adjustFireLoss(-3.5) - else if(isanimal(L)) - var/mob/living/simple_animal/SM = L - SM.adjustHealth(-3.5, forced = TRUE) /datum/status_effect/hippocratic_oath/proc/consume_owner() owner.visible_message(span_notice("[owner]'s soul is absorbed into the rod, relieving the previous snake of its duty.")) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 43db3103326..5ed6656de6e 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -69,6 +69,13 @@ if(cult_team.blood_target && cult_team.blood_target_image && current.client) current.client.images += cult_team.blood_target_image + ADD_TRAIT(current, TRAIT_HEALS_FROM_CULT_PYLONS, CULT_TRAIT) + +/datum/antagonist/cult/on_removal() + REMOVE_TRAIT(owner.current, TRAIT_HEALS_FROM_CULT_PYLONS, CULT_TRAIT) + + return ..() + /datum/antagonist/cult/get_preview_icon() var/icon/icon = render_preview_outfit(preview_outfit) diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm index d74d4bcac8b..0c22bdd5ea2 100644 --- a/code/modules/antagonists/cult/cult_structures.dm +++ b/code/modules/antagonists/cult/cult_structures.dm @@ -179,13 +179,25 @@ light_color = COLOR_SOFT_RED break_sound = 'sound/effects/glassbr2.ogg' break_message = "The blood-red crystal falls to the floor and shatters!" - var/heal_delay = 25 var/last_heal = 0 var/corrupt_delay = 50 var/last_corrupt = 0 /obj/structure/destructible/cult/pylon/Initialize(mapload) . = ..() + + AddComponent( \ + /datum/component/aura_healing, \ + range = 5, \ + brute_heal = 0.4, \ + burn_heal = 0.4, \ + blood_heal = 0.4, \ + simple_heal = 1.2, \ + requires_visibility = FALSE, \ + limit_to_trait = TRAIT_HEALS_FROM_CULT_PYLONS, \ + healing_color = COLOR_CULT_RED, \ + ) + START_PROCESSING(SSfastprocess, src) /obj/structure/destructible/cult/pylon/Destroy() @@ -195,23 +207,6 @@ /obj/structure/destructible/cult/pylon/process() if(!anchored) return - if(last_heal <= world.time) - last_heal = world.time + heal_delay - for(var/mob/living/L in range(5, src)) - if(IS_CULTIST(L) || isshade(L) || isconstruct(L)) - if(L.health != L.maxHealth) - new /obj/effect/temp_visual/heal(get_turf(src), "#960000") - if(ishuman(L)) - L.adjustBruteLoss(-1, 0) - L.adjustFireLoss(-1, 0) - L.updatehealth() - if(isshade(L) || isconstruct(L)) - var/mob/living/simple_animal/M = L - if(M.health < M.maxHealth) - M.adjustHealth(-3) - if(ishuman(L) && L.blood_volume < BLOOD_VOLUME_NORMAL) - L.blood_volume += 1.0 - CHECK_TICK if(last_corrupt <= world.time) var/list/validturfs = list() var/list/cultturfs = list() diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 9220aa12b2f..6726d69f697 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -48,6 +48,7 @@ /mob/living/simple_animal/hostile/construct/Initialize(mapload) . = ..() AddElement(/datum/element/simple_flying) + ADD_TRAIT(src, TRAIT_HEALS_FROM_CULT_PYLONS, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) var/spellnum = 1 for(var/spell in construct_spells) diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm index 7b47716e271..1559ebaba32 100644 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ b/code/modules/mob/living/simple_animal/hostile/carp.dm @@ -73,6 +73,7 @@ set_greyscale(new_config=/datum/greyscale_config/carp) carp_randomify(rarechance) . = ..() + ADD_TRAIT(src, TRAIT_HEALS_FROM_CARP_RIFTS, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) add_cell_sample() if(ai_controller) diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index 1fc2a47cbcc..b2a8ddcd703 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -568,6 +568,15 @@ /obj/structure/carp_rift/Initialize(mapload) . = ..() + + AddComponent( \ + /datum/component/aura_healing, \ + range = 0, \ + simple_heal = 5, \ + limit_to_trait = TRAIT_HEALS_FROM_CARP_RIFTS, \ + healing_color = COLOR_BLUE, \ + ) + START_PROCESSING(SSobj, src) // Carp rifts always take heavy explosion damage. Discourages the use of maxcaps @@ -598,13 +607,6 @@ return ..() /obj/structure/carp_rift/process(delta_time) - // Heal carp on our loc. - for(var/mob/living/simple_animal/hostile/hostilehere in loc) - if("carp" in hostilehere.faction) - hostilehere.adjustHealth(-5 * delta_time) - var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(hostilehere)) - H.color = "#0000FF" - // If we're fully charged, just start mass spawning carp and move around. if(charge_state == CHARGE_COMPLETED) if(DT_PROB(1.25, delta_time)) diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index 8b9038cb730..b95bdf99b02 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -37,6 +37,7 @@ /mob/living/simple_animal/shade/Initialize(mapload) . = ..() AddElement(/datum/element/simple_flying) + ADD_TRAIT(src, TRAIT_HEALS_FROM_CULT_PYLONS, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) diff --git a/tgstation.dme b/tgstation.dme index d6043364841..ea700f35bae 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -462,6 +462,7 @@ #include "code\controllers\subsystem\processing\ai_basic_avoidance.dm" #include "code\controllers\subsystem\processing\ai_behaviors.dm" #include "code\controllers\subsystem\processing\ai_movement.dm" +#include "code\controllers\subsystem\processing\aura_healing.dm" #include "code\controllers\subsystem\processing\clock_component.dm" #include "code\controllers\subsystem\processing\fastprocess.dm" #include "code\controllers\subsystem\processing\fields.dm" @@ -605,6 +606,7 @@ #include "code\datums\components\area_sound_manager.dm" #include "code\datums\components\areabound.dm" #include "code\datums\components\armor_plate.dm" +#include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" #include "code\datums\components\beetlejuice.dm" #include "code\datums\components\bloodysoles.dm"