FINALLY!! Plant bodyparts will heal when transplanted (#89306)

Finally, I've done it.

You can get plant limbs attached, and those limbs will actually
regenerate in light!

I've done the same for shadow healing for nightmares and shadowpeople.

![image](https://github.com/user-attachments/assets/bfcc30eb-2c05-4512-85f1-e44a378aa8e7)

**Details:**
I've removed the healing/damage from their species, and instead have the
prospective limbs add a new /datum/status_effect/bodypart_effect to the
carbon, which other bodyparts with the same effect merge into.

Plant people in specific also had oxy and tox healing, as well as
nutritional gain. These attributes have been integrated into the
bodyparts aswell, and scaled to the amount of bodyparts you have. You
can take 1 plant limb to get 1/6th of the oxygen and toxin healing of a
normal podperson. Same for nutritional gain. (Fun fact: you need 2 plant
limbs to never lose hunger, 3 to actually be nutrition positive!) Damage
wise, if you have 1 plant limb, you get 1/6th the plant healing but
focused on only the plant limb. (This is hopefully exactly as you'd
expect).

Fun fact: if you remove your lungs and organs and take plant limbs, you
will still die from oxygen loss or toxin loss because the oxy and tox
healing is really weak.

Honestly this is peak sandbox. I remember when transplants got added,
and I took a plant limb, but it didn't heal in light :(
This shattered my immersion and deeply saddened me. Honestly this is why
I started the whole dedatumisation of species tirade.

But it's here. Trans plant plant transplants will heal in light.
Tgstation is saved and all furry downstreams are rebasing.

🆑
add: Taking plant or shadow limbs will now behave as expected! Take a
plant arm, and it will heal in light! Or take plant and shadow limbs and
be in constant agony as you realize this world was not made for you
refactor: Refactors how we handle photosynthesis/nyxosynthesis by moving
it into a new status_effect framework controlled by podyparts
/🆑
This commit is contained in:
Time-Green
2025-03-12 16:46:36 -04:00
committed by Roxy
parent 8e5bee4d74
commit a57ee5ef64
11 changed files with 224 additions and 58 deletions
+4
View File
@@ -100,6 +100,10 @@
#define BODYTYPE_GOLEM (1<<4)
//The limb is a peg limb
#define BODYTYPE_PEG (1<<5)
//The limb is plantly (and will regen if photosynthesis is active)
#define BODYTYPE_PLANT (1<<6)
//This limb is shadowy and will regen if shadowheal is active
#define BODYTYPE_SHADOW (1<<7)
// SKYRAT EDIT ADDITION
///The limb fits a modular custom shape
#define BODYSHAPE_CUSTOM (1<<9)
+11 -7
View File
@@ -615,29 +615,33 @@
desc = "You're immune to radiation, get settled quick!"
icon_state = "radiation_shield"
/// Heal in darkness and potentially trigger other effects, persists for a short duration after leaving
/datum/status_effect/shadow_regeneration
id = "shadow_regeneration"
/// Throw an alert we're in darkness!! Nightvision can make it hard to tell so this is useful
/datum/status_effect/shadow
id = "shadow"
duration = 2 SECONDS
status_type = STATUS_EFFECT_REFRESH
alert_type = /atom/movable/screen/alert/status_effect/shadow_regeneration
/datum/status_effect/shadow_regeneration/on_apply()
/// Same as above, but also heal in darkness!! Mostly superseded but some simple mobs use this
/datum/status_effect/shadow/regeneration
id = "shadow_regeneration"
/datum/status_effect/shadow/regeneration/on_apply()
. = ..()
if (!.)
return FALSE
heal_owner()
return TRUE
/datum/status_effect/shadow_regeneration/refresh(effect)
/datum/status_effect/shadow/regeneration/refresh(effect)
. = ..()
heal_owner()
/// Regenerate health whenever this status effect is applied or reapplied
/datum/status_effect/shadow_regeneration/proc/heal_owner()
/datum/status_effect/shadow/regeneration/proc/heal_owner()
owner.heal_overall_damage(brute = 1, burn = 1, required_bodytype = BODYTYPE_ORGANIC)
/atom/movable/screen/alert/status_effect/shadow_regeneration
name = "Shadow Regeneration"
desc = "Bathed in soothing darkness, you will slowly heal yourself."
desc = "Bathed in soothing darkness, you will slowly heal yourself"
icon_state = "lightless"
@@ -3,13 +3,12 @@
/// A special flag value used to make a nightmare heart not grant a light eater. Appears to be unused.
#define HEART_SPECIAL_SHADOWIFY 2
/obj/item/organ/brain/shadow/nightmare
name = "tumorous mass"
desc = "A fleshy growth that was dug out of the skull of a Nightmare."
icon = 'icons/obj/medical/organs/organs.dmi'
icon_state = "brain-x-d"
applied_status = /datum/status_effect/shadow_regeneration/nightmare
///Our associated shadow jaunt spell, for all nightmares
var/datum/action/cooldown/spell/jaunt/shadow_walk/our_jaunt
///Our associated terrorize spell, for antagonist nightmares
@@ -34,26 +33,35 @@
QDEL_NULL(our_jaunt)
QDEL_NULL(terrorize_spell)
/atom/movable/screen/alert/status_effect/shadow_regeneration/nightmare
name = "Lightless Domain"
desc = "Bathed in soothing darkness you will slowly regenerate, even past the point of death. \
Heightened reflexes will allow you to dodge projectile weapons."
/obj/item/organ/brain/shadow/nightmare/on_life(seconds_per_tick, times_fired)
. = ..()
/datum/status_effect/shadow_regeneration/nightmare
var/turf/owner_turf = owner.loc
if(!isturf(owner_turf))
return
var/light_amount = owner_turf.get_lumcount()
if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //dodge in the dark
owner.apply_status_effect(/datum/status_effect/shadow/nightmare)
/datum/status_effect/shadow/nightmare
id = "nightmare"
duration = 2 SECONDS
status_type = STATUS_EFFECT_REFRESH
alert_type = /atom/movable/screen/alert/status_effect/shadow_regeneration/nightmare
/datum/status_effect/shadow_regeneration/nightmare/on_apply()
/datum/status_effect/shadow/nightmare/on_apply()
. = ..()
if (!.)
return FALSE
RegisterSignal(owner, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(dodge_bullets))
return TRUE
/datum/status_effect/shadow_regeneration/nightmare/on_remove()
/datum/status_effect/shadow/nightmare/on_remove()
UnregisterSignal(owner, COMSIG_ATOM_PRE_BULLET_ACT)
return ..()
/datum/status_effect/shadow_regeneration/nightmare/proc/dodge_bullets(mob/living/carbon/human/source, obj/projectile/hitting_projectile, def_zone)
/datum/status_effect/shadow/nightmare/proc/dodge_bullets(mob/living/carbon/human/source, obj/projectile/hitting_projectile, def_zone)
SIGNAL_HANDLER
source.visible_message(
span_danger("[source] dances in the shadows, evading [hitting_projectile]!"),
@@ -62,6 +70,11 @@
playsound(source, SFX_BULLET_MISS, 75, TRUE)
return COMPONENT_BULLET_PIERCED
/atom/movable/screen/alert/status_effect/shadow_regeneration/nightmare
name = "Lightless Domain"
desc = "Bathed in soothing darkness you will slowly regenerate, even past the point of death. \
Heightened reflexes will allow you to dodge projectile weapons."
/obj/item/organ/heart/nightmare
name = "heart of darkness"
desc = "An alien organ that twists and writhes when exposed to light."
+1 -1
View File
@@ -209,7 +209,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
var/light_amount = our_turf.get_lumcount()
if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark
mob.apply_status_effect(/datum/status_effect/shadow_regeneration)
mob.apply_status_effect(/datum/status_effect/shadow/regeneration)
/datum/fish_trait/heavy
name = "Demersal"
+5
View File
@@ -1068,6 +1068,11 @@
bodyparts += new_bodypart
new_bodypart.update_owner(src)
// Apply a bodypart effect or merge with an existing one, for stuff like plant limbs regenning in light
for(var/datum/status_effect/grouped/bodypart_effect/effect_type as anything in new_bodypart.bodypart_effects)
apply_status_effect(effect_type, type, new_bodypart)
// Tell the organs in the bodyparts that we are in a mob again
for(var/obj/item/organ/organ in new_bodypart)
organ.mob_insert(src)
@@ -39,30 +39,6 @@
BODY_ZONE_CHEST = /obj/item/bodypart/chest/pod,
)
/datum/species/pod/spec_life(mob/living/carbon/human/podperson, seconds_per_tick, times_fired)
. = ..()
if(podperson.stat == DEAD)
return
var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
if(isturf(podperson.loc)) //else, there's considered to be no light
var/turf/turf_loc = podperson.loc
light_amount = min(1, turf_loc.get_lumcount()) - 0.5
podperson.adjust_nutrition(5 * light_amount * seconds_per_tick)
if(light_amount > 0.2) //if there's enough light, heal
var/need_mob_update = FALSE
need_mob_update += podperson.heal_overall_damage(brute = 0.5 * seconds_per_tick, burn = 0.5 * seconds_per_tick, updating_health = FALSE, required_bodytype = BODYTYPE_ORGANIC)
need_mob_update += podperson.adjustToxLoss(-0.5 * seconds_per_tick, updating_health = FALSE)
need_mob_update += podperson.adjustOxyLoss(-0.5 * seconds_per_tick, updating_health = FALSE)
if(need_mob_update)
podperson.updatehealth()
if(podperson.nutrition > NUTRITION_LEVEL_ALMOST_FULL) //don't make podpeople fat because they stood in the sun for too long
podperson.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL)
if(podperson.nutrition < NUTRITION_LEVEL_STARVING + 50)
podperson.take_overall_damage(brute = 1 * seconds_per_tick, required_bodytype = BODYTYPE_ORGANIC)
/datum/species/pod/handle_chemical(datum/reagent/chem, mob/living/carbon/human/affected, seconds_per_tick, times_fired)
. = ..()
if(. & COMSIG_MOB_STOP_REAGENT_CHECK)
@@ -98,22 +98,8 @@
pepperspray_protect = TRUE
flash_protect = FLASH_PROTECTION_SENSITIVE
/// the key to some of their powers
/// the key to none of their powers
/obj/item/organ/brain/shadow
name = "shadowling tumor"
desc = "Something that was once a brain, before being remolded by a shadowling. It has adapted to the dark, irreversibly."
icon = 'icons/obj/medical/organs/shadow_organs.dmi'
/// What status effect do we gain while in darkness?
var/applied_status = /datum/status_effect/shadow_regeneration
/obj/item/organ/brain/shadow/on_life(seconds_per_tick, times_fired)
. = ..()
var/turf/owner_turf = owner.loc
if(!isturf(owner_turf))
return
var/light_amount = owner_turf.get_lumcount()
if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark
owner.apply_status_effect(applied_status)
if (!owner.has_status_effect(applied_status))
owner.take_overall_damage(brute = 0.5 * seconds_per_tick, burn = 0.5 * seconds_per_tick, required_bodytype = BODYTYPE_ORGANIC)
+2 -1
View File
@@ -203,6 +203,8 @@
var/robotic_emp_paralyze_damage_percent_threshold = 0.3
/// A potential texturing overlay to put on the limb
var/datum/bodypart_overlay/texture/texture_bodypart_overlay
/// Lazylist of /datum/status_effect/grouped/bodypart_effect types. Instances of this are applied to the carbon when added the limb is attached, and merged with similair limbs
var/list/bodypart_effects
// SKYRAT EDIT BEGIN
/// If we even wanna try and handle icons/overlays of the limb (Taurs don't, f.e.). See update_body_parts
var/show_icon = TRUE
@@ -269,7 +271,6 @@
return FALSE
return ..()
/obj/item/bodypart/proc/on_forced_removal(atom/old_loc, dir, forced, list/old_locs)
SIGNAL_HANDLER
@@ -0,0 +1,134 @@
/// For scaling the effectiveness of certain effects to the total bodypart count
#define GET_BODYPART_COEFFICIENT(X) round(X.len / BODYPARTS_DEFAULT_MAXIMUM , 0.1)
/// Check if it's full body. These are mostly here so we can change just one place when we ever add more limbs (?)
#define IS_FULL_BODY(X) (X.len == BODYPARTS_DEFAULT_MAXIMUM )
/// Effects added to a carbon focused on the bodyparts itself, such as adding a photosynthesis component that
/datum/status_effect/grouped/bodypart_effect
id = "bodypart_effect"
duration = STATUS_EFFECT_PERMANENT
processing_speed = STATUS_EFFECT_NORMAL_PROCESS
/// List of bodyparts contributing to this effect
var/list/bodyparts = list()
/// Minimum amount of bodyparts required for on_apply to be called. When tipping below, on_remove is called
var/minimum_bodyparts = 1
/// Are we currently active? We don't NEED to track it, but it's a lot easier and faster if we do
var/is_active = FALSE
/datum/status_effect/grouped/bodypart_effect/source_added(source, obj/item/bodypart/bodypart)
add_bodypart(bodypart)
/// Merge a bodypart into the effect
/datum/status_effect/grouped/bodypart_effect/proc/add_bodypart(bodypart)
RegisterSignal(bodypart, COMSIG_BODYPART_REMOVED, PROC_REF(on_bodypart_removed))
RegisterSignal(bodypart, COMSIG_QDELETING, PROC_REF(on_bodypart_destroyed))
bodyparts.Add(bodypart)
if(!is_active && bodyparts.len >= minimum_bodyparts)
activate()
/// Remove a bodypart from the effect. Deleting = TRUE is used during clean-up phase
/datum/status_effect/grouped/bodypart_effect/proc/remove_bodypart(obj/item/bodypart/bodypart, deleting = FALSE)
UnregisterSignal(bodypart, COMSIG_BODYPART_REMOVED)
bodyparts.Remove(bodypart)
if(deleting)
return
if(bodyparts.len == 0)
qdel(src)
else if(is_active && bodyparts.len < minimum_bodyparts)
deactivate()
/// Signal called when a bodypart is removed
/datum/status_effect/grouped/bodypart_effect/proc/on_bodypart_removed(obj/item/bodypart/bodypart)
SIGNAL_HANDLER
remove_bodypart(owner, bodypart)
/// Signal called when a bodypart is destroyed. Destruction of a bodypart doesn't necessarily drop it
/datum/status_effect/grouped/bodypart_effect/proc/on_bodypart_destroyed(obj/item/bodypart/bodypart)
SIGNAL_HANDLER
remove_bodypart(bodypart)
/// Activate some sort of effect when a threshold is reached
/datum/status_effect/grouped/bodypart_effect/proc/activate()
PROTECTED_PROC(TRUE)
SHOULD_CALL_PARENT(TRUE)
is_active = TRUE
// Maybe add support for different stages? AKA add a stronger effect when there are more limbs
/// Remove an effect whenever a threshold is no longer reached
/datum/status_effect/grouped/bodypart_effect/proc/deactivate()
PROTECTED_PROC(TRUE)
SHOULD_CALL_PARENT(TRUE)
is_active = FALSE
/// Clean up all references and self-destruct
/datum/status_effect/grouped/bodypart_effect/Destroy()
deactivate()
for(var/obj/item/bodypart/bodypart as anything in bodyparts)
remove_bodypart(bodypart, deleting = TRUE)
return ..()
/// This limb regens in light! Only BODYTYPE_PLANT limbs will heal, but limbs without the flag (and with the effect) still contribute to healing of the other limbs
/datum/status_effect/grouped/bodypart_effect/photosynthesis
processing_speed = STATUS_EFFECT_NORMAL_PROCESS
tick_interval = 1 SECONDS
id = "photosynthesis"
/datum/status_effect/grouped/bodypart_effect/photosynthesis/tick(seconds_between_ticks)
var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
var/bodypart_coefficient = GET_BODYPART_COEFFICIENT(bodyparts)
if(isturf(owner.loc)) //else, there's considered to be no light
var/turf/turf_loc = owner.loc
light_amount = min(1, turf_loc.get_lumcount()) - 0.5
if(owner.nutrition < NUTRITION_LEVEL_ALMOST_FULL)
owner.adjust_nutrition(5 * light_amount * bodypart_coefficient)
if(light_amount > 0.2) //if there's enough light, heal
var/need_mob_update = FALSE
need_mob_update += owner.heal_overall_damage(brute = 0.5 * bodypart_coefficient, \
burn = 0.5 * bodypart_coefficient, updating_health = FALSE, required_bodytype = BODYTYPE_PLANT)
need_mob_update += owner.adjustToxLoss(-0.5 * bodypart_coefficient, updating_health = FALSE)
need_mob_update += owner.adjustOxyLoss(-0.5 * bodypart_coefficient, updating_health = FALSE)
if(need_mob_update)
owner.updatehealth()
if(owner.nutrition < NUTRITION_LEVEL_STARVING + 50)
owner.take_overall_damage(brute = 1 * bodypart_coefficient, required_bodytype = BODYTYPE_PLANT)
/// This limb heals in darkness and dies in light!
/// Only BODYTYPE_SHADOW limbs will heal, but limbs without the flag (and with the effect) still contribute to healing of the other limbs
/datum/status_effect/grouped/bodypart_effect/nyxosynthesis
tick_interval = 1 SECONDS
id = "nyxosynthesis"
/datum/status_effect/grouped/bodypart_effect/nyxosynthesis/tick(seconds_between_ticks)
var/turf/owner_turf = owner.loc
if(!isturf(owner_turf))
return
var/light_amount = owner_turf.get_lumcount()
var/bodypart_coefficient = GET_BODYPART_COEFFICIENT(bodyparts)
if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark
owner.heal_overall_damage(brute = 0.5 * bodypart_coefficient, burn = 0.5 * bodypart_coefficient, required_bodytype = BODYTYPE_SHADOW)
if(!owner.has_status_effect(/datum/status_effect/shadow/nightmare)) //somewhat awkward, but let's not duplicate the alerts
//this only appears when in shadows, don't move to bodypart effect so people with nightvision can still tell if they're in light or not
owner.apply_status_effect(/datum/status_effect/shadow)
else
owner.take_overall_damage(brute = 1 * bodypart_coefficient, burn = 1 * bodypart_coefficient, required_bodytype = BODYTYPE_SHADOW)
#undef GET_BODYPART_COEFFICIENT
#undef IS_FULL_BODY
@@ -212,12 +212,18 @@
burn_modifier = 1.25
head_flags = HEAD_EYESPRITES|HEAD_EYECOLOR|HEAD_EYEHOLES|HEAD_DEBRAIN
bodytype = BODYTYPE_ORGANIC | BODYTYPE_PLANT
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/photosynthesis)
/obj/item/bodypart/chest/pod
limb_id = SPECIES_PODPERSON
is_dimorphic = TRUE
burn_modifier = 1.25
wing_types = null
bodytype = BODYTYPE_ORGANIC | BODYTYPE_PLANT
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/photosynthesis)
/obj/item/bodypart/chest/pod/get_butt_sprite()
return icon('icons/mob/butts.dmi', BUTT_SPRITE_FLOWERPOT)
@@ -230,6 +236,9 @@
unarmed_miss_sound = 'sound/items/weapons/slashmiss.ogg'
burn_modifier = 1.25
bodytype = BODYTYPE_ORGANIC | BODYTYPE_PLANT
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/photosynthesis)
/obj/item/bodypart/arm/right/pod
limb_id = SPECIES_PODPERSON
unarmed_attack_verbs = list("slash", "lash")
@@ -239,14 +248,23 @@
unarmed_miss_sound = 'sound/items/weapons/slashmiss.ogg'
burn_modifier = 1.25
bodytype = BODYTYPE_ORGANIC | BODYTYPE_PLANT
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/photosynthesis)
/obj/item/bodypart/leg/left/pod
limb_id = SPECIES_PODPERSON
burn_modifier = 1.25
bodytype = BODYTYPE_ORGANIC | BODYTYPE_PLANT
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/photosynthesis)
/obj/item/bodypart/leg/right/pod
limb_id = SPECIES_PODPERSON
burn_modifier = 1.25
bodytype = BODYTYPE_ORGANIC | BODYTYPE_PLANT
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/photosynthesis)
///FLY
/obj/item/bodypart/head/fly
limb_id = SPECIES_FLYPERSON
@@ -287,6 +305,9 @@
burn_modifier = 1.5
head_flags = NONE
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/chest/shadow
limb_id = SPECIES_SHADOW
is_dimorphic = FALSE
@@ -294,32 +315,53 @@
burn_modifier = 1.5
wing_types = null
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/arm/left/shadow
limb_id = SPECIES_SHADOW
should_draw_greyscale = FALSE
burn_modifier = 1.5
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/arm/right/shadow
limb_id = SPECIES_SHADOW
should_draw_greyscale = FALSE
burn_modifier = 1.5
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/leg/left/shadow
limb_id = SPECIES_SHADOW
should_draw_greyscale = FALSE
burn_modifier = 1.5
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/leg/right/shadow
limb_id = SPECIES_SHADOW
should_draw_greyscale = FALSE
burn_modifier = 1.5
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/arm/left/shadow/nightmare
bodypart_traits = list(TRAIT_CHUNKYFINGERS)
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
/obj/item/bodypart/arm/right/shadow/nightmare
bodypart_traits = list(TRAIT_CHUNKYFINGERS)
bodypart_effects = list(/datum/status_effect/grouped/bodypart_effect/nyxosynthesis)
bodytype = BODYTYPE_ORGANIC | BODYTYPE_SHADOW
///SKELETON
/obj/item/bodypart/head/skeleton
biological_state = BIO_BONE
+1
View File
@@ -6273,6 +6273,7 @@
#include "code\modules\surgery\advanced\bioware\nerve_splicing.dm"
#include "code\modules\surgery\advanced\bioware\vein_threading.dm"
#include "code\modules\surgery\bodyparts\_bodyparts.dm"
#include "code\modules\surgery\bodyparts\bodypart_effects.dm"
#include "code\modules\surgery\bodyparts\dismemberment.dm"
#include "code\modules\surgery\bodyparts\ghetto_parts.dm"
#include "code\modules\surgery\bodyparts\head.dm"