From 29c592d5149de2eb4937bad86e4d2b9be0104dac Mon Sep 17 00:00:00 2001
From: Timothy Teakettle <59849408+timothyteakettle@users.noreply.github.com>
Date: Sun, 21 Dec 2025 12:22:47 +0000
Subject: [PATCH] rewrites holosphere health code (#7408)
---
citadel.dme | 3 +
code/__DEFINES/transform.dm | 5 ++
code/datums/components/custom_transform.dm | 13 +++-
code/modules/species/holosphere/health.dm | 76 +++++++++----------
code/modules/species/holosphere/holosphere.dm | 33 +-------
.../species/holosphere/holosphere_shell.dm | 1 +
code/modules/species/holosphere/transform.dm | 41 ++++++++++
code/modules/species/holosphere/verbs.dm | 10 +++
8 files changed, 111 insertions(+), 71 deletions(-)
create mode 100644 code/__DEFINES/transform.dm
create mode 100644 code/modules/species/holosphere/transform.dm
create mode 100644 code/modules/species/holosphere/verbs.dm
diff --git a/citadel.dme b/citadel.dme
index 91f744d9f41..3bf1db94f73 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -106,6 +106,7 @@
#include "code\__DEFINES\tgs.dm"
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\time.dm"
+#include "code\__DEFINES\transform.dm"
#include "code\__DEFINES\turfs.dm"
#include "code\__DEFINES\typeids.dm"
#include "code\__DEFINES\unit_tests.dm"
@@ -5281,6 +5282,8 @@
#include "code\modules\species\holosphere\health.dm"
#include "code\modules\species\holosphere\holosphere.dm"
#include "code\modules\species\holosphere\holosphere_shell.dm"
+#include "code\modules\species\holosphere\transform.dm"
+#include "code\modules\species\holosphere\verbs.dm"
#include "code\modules\species\outsider\celestials.dm"
#include "code\modules\species\outsider\replicant.dm"
#include "code\modules\species\outsider\scori.dm"
diff --git a/code/__DEFINES/transform.dm b/code/__DEFINES/transform.dm
new file mode 100644
index 00000000000..729731a99a5
--- /dev/null
+++ b/code/__DEFINES/transform.dm
@@ -0,0 +1,5 @@
+// defines for transformation states
+// per the custom_transform component
+#define STATE_TRANSFORMED "transformed"
+#define STATE_NOT_TRANSFORMED "not_transformed"
+#define STATE_UNKNOWN_TRANSFORM "unknown"
diff --git a/code/datums/components/custom_transform.dm b/code/datums/components/custom_transform.dm
index a879db70be2..c15246f6f75 100644
--- a/code/datums/components/custom_transform.dm
+++ b/code/datums/components/custom_transform.dm
@@ -30,7 +30,7 @@
owner.visible_message("[owner] [transform_text]")
/datum/component/custom_transform/proc/try_transform(silent = FALSE)
- if(transformed.loc == parent)
+ if(get_current_transform_state() == STATE_NOT_TRANSFORMED)
transform(silent)
return TRUE
return FALSE
@@ -45,8 +45,7 @@
owner.visible_message("[owner] [untransform_text]")
/datum/component/custom_transform/proc/try_untransform(silent = FALSE)
- var/mob/owner = parent
- if(owner.loc == transformed)
+ if(get_current_transform_state() == STATE_TRANSFORMED)
untransform(silent)
return TRUE
return FALSE
@@ -102,3 +101,11 @@
S.mob_radio = null
H.forceMove(get_turf(M))
M.transfer_client_to(H)
+
+/datum/component/custom_transform/proc/get_current_transform_state()
+ var/mob/M = parent
+ if(transformed.loc == M)
+ return STATE_NOT_TRANSFORMED
+ if(M.loc == transformed)
+ return STATE_TRANSFORMED
+ return STATE_UNKNOWN_TRANSFORM
diff --git a/code/modules/species/holosphere/health.dm b/code/modules/species/holosphere/health.dm
index 7cec17706c7..18c9466c357 100644
--- a/code/modules/species/holosphere/health.dm
+++ b/code/modules/species/holosphere/health.dm
@@ -1,51 +1,30 @@
-/datum/species/shapeshifter/holosphere/handle_death(var/mob/living/carbon/human/H, gibbed)
- last_death_time = world.time
+/datum/species/shapeshifter/holosphere/handle_environment_special(mob/living/carbon/human/H, datum/gas_mixture/environment, dt)
+ // handle_crit does not transform the shell on crit, it does everything else (the message, stun, automatic attempt to revive)
+ if(holosphere_shell.hologram.is_in_critical())
+ handle_crit(H)
+ // handles when you are forced to change state between transformed/untransformed (i.e. due to dying or being in crit)
+ handle_transform_state()
+
+ handle_regen(H)
+
+/datum/species/shapeshifter/holosphere/handle_death(var/mob/living/carbon/human/H, gibbed)
if(gibbed)
QDEL_NULL(holosphere_shell)
return
+/datum/species/shapeshifter/holosphere/proc/handle_crit(mob/living/carbon/human/H)
+ if(get_current_transform_state() == STATE_TRANSFORMED)
+ return
+
var/deathmsg = "Systems critically damaged. Emitters temporarily offline."
- to_chat(H, deathmsg)
- try_transform(force = TRUE)
+ to_chat(holosphere_shell, deathmsg)
holosphere_shell.afflict_stun(hologram_death_duration)
spawn(hologram_death_duration)
try_revive(H)
-/// same way shapeshifter species heals but it does not work if you have no nutrition
-/datum/species/shapeshifter/holosphere/handle_environment_special(mob/living/carbon/human/H, datum/gas_mixture/environment, dt)
- if(!actively_healing || H.nutrition <= 0)
- return
- if(H.fire_stacks >= 0 && heal_rate > 0)
- if(H.getBruteLoss() || H.getFireLoss() || H.getOxyLoss() || H.getToxLoss())
- var/nutrition_cost = 0
- var/nutrition_debt = H.getBruteLoss()
- var/starve_mod = 1
- if(H.nutrition <= 25)
- starve_mod = 0.75
- H.adjustBruteLoss(-heal_rate * starve_mod)
- nutrition_cost += nutrition_debt - H.getBruteLoss()
-
- nutrition_debt = H.getFireLoss()
- H.adjustFireLoss(-heal_rate * starve_mod)
- nutrition_cost += nutrition_debt - H.getFireLoss()
-
- nutrition_debt = H.getOxyLoss()
- H.adjustOxyLoss(-heal_rate * starve_mod)
- nutrition_cost += nutrition_debt - H.getOxyLoss()
-
- nutrition_debt = H.getToxLoss()
- H.adjustToxLoss(-heal_rate * starve_mod)
- nutrition_cost += nutrition_debt - H.getToxLoss()
- H.nutrition -= (heal_nutrition_multiplier * nutrition_cost) //Costs Nutrition when damage is being repaired, corresponding to the amount of damage being repaired.
- H.nutrition = max(0, H.nutrition) //Ensure it's not below 0.
- try_revive(H, TRUE)
-
-/datum/species/shapeshifter/holosphere/proc/get_revive_cost()
- return max_nutrition / 4
-
/datum/species/shapeshifter/holosphere/proc/can_revive(mob/living/carbon/human/H, revive_cost)
- if(H.stat != DEAD)
+ if(H.stat != DEAD && !H.is_in_critical())
return FALSE
if(holosphere_shell.stat == DEAD)
return FALSE
@@ -56,14 +35,35 @@
return FALSE
return TRUE
+/datum/species/shapeshifter/holosphere/proc/get_revive_cost()
+ return max_nutrition / 4
+
/datum/species/shapeshifter/holosphere/proc/try_revive(mob/living/carbon/human/H, silent_failure = FALSE)
var/revive_cost = get_revive_cost()
if(can_revive(H, revive_cost))
H.nutrition -= revive_cost
try_untransform(force = TRUE)
- H.revive(full_heal = TRUE, restore_nutrition = FALSE)
+ if(IS_DEAD(H))
+ H.revive(full_heal = TRUE, restore_nutrition = FALSE)
+ else
+ H.rejuvenate(TRUE, restore_nutrition = FALSE)
var/regenmsg = "Emitters have returned online. Systems functional."
to_chat(H, regenmsg)
else if(!silent_failure)
var/failuremsg = "Emitters unable to turn online due to insufficient battery."
to_chat(holosphere_shell, failuremsg)
+
+/datum/species/shapeshifter/holosphere/proc/handle_regen(mob/living/carbon/human/H)
+ var/nutrition_cost = heal_nutrition_multiplier * heal_rate
+ if(H.getBruteLoss() && H.nutrition >= nutrition_cost)
+ H.nutrition -= nutrition_cost
+ H.adjustBruteLoss(-heal_rate, TRUE)
+ if(H.getFireLoss() && H.nutrition >= nutrition_cost)
+ H.nutrition -= nutrition_cost
+ H.adjustFireLoss(-heal_rate, TRUE)
+ if(H.getOxyLoss() && H.nutrition >= nutrition_cost)
+ H.nutrition -= nutrition_cost
+ H.adjustOxyLoss(-heal_rate)
+ if(H.getToxLoss() && H.nutrition >= nutrition_cost)
+ H.nutrition -= nutrition_cost
+ H.adjustToxLoss(-heal_rate)
diff --git a/code/modules/species/holosphere/holosphere.dm b/code/modules/species/holosphere/holosphere.dm
index 7660c7b0d0d..ae8e5bbcf1e 100644
--- a/code/modules/species/holosphere/holosphere.dm
+++ b/code/modules/species/holosphere/holosphere.dm
@@ -25,9 +25,9 @@
species_appearance_flags = HAS_HAIR_COLOR | HAS_SKIN_COLOR | HAS_LIPS | HAS_UNDERWEAR | HAS_EYE_COLOR | HAS_BODY_ALPHA | HAS_HAIR_ALPHA
species_flags = NO_SCAN | NO_SLIP | NO_MINOR_CUT | NO_HALLUCINATION | NO_INFECT | NO_PAIN | CONTAMINATION_IMMUNE | NO_BLOOD | NO_NUTRITION_GAIN
- total_health = 20
+ total_health = 200
death_health = 0
- crit_health = -100 // never happens because you hit death health first, and cant go into crit while dead
+ crit_health = 180 // they turn into their shell at crit, so effectively they have 20 health
hunger_factor = 0 // doesn't get hungry naturally, but instead when healing they use nutrition
@@ -156,34 +156,6 @@
remove_chameleon_gear()
-/datum/species/shapeshifter/holosphere/proc/try_transform(force = FALSE)
- if(force || !IS_DEAD(holosphere_shell))
- if(holosphere_shell.hologram.incapacitated(INCAPACITATION_ALL))
- to_chat(holosphere_shell.hologram, SPAN_WARNING("You can't do that right now!"))
- return
-
- holosphere_shell.name = holosphere_shell.hologram.name
- try_exit_recharge_station()
- if(transform_component.try_transform())
- holosphere_shell.hologram.drop_held_items()
- holosphere_shell.regenerate_icons()
-
-/datum/species/shapeshifter/holosphere/proc/try_untransform(force = FALSE)
- if(force || !IS_DEAD(holosphere_shell.hologram))
- try_exit_recharge_station()
- transform_component.try_untransform()
-
-/mob/living/carbon/human/proc/disable_hologram()
- set name = "Disable Hologram (Holosphere)"
- set desc = "Disable your hologram."
- set category = VERB_CATEGORY_IC
-
- var/datum/species/shapeshifter/holosphere/holosphere_species = species
- if(!istype(holosphere_species))
- return
-
- holosphere_species.try_transform()
-
/datum/species/shapeshifter/holosphere/apply_survival_gear(mob/living/carbon/for_target, list/into_box, list/into_inv)
into_box?.Add(/obj/item/tool/prybar/red)
into_box?.Add(/obj/item/flashlight/flare/survival)
@@ -204,6 +176,7 @@
SPECIES_MONKEY_TAJ, SPECIES_MONKEY_AKULA, SPECIES_MONKEY_VULPKANIN,
SPECIES_MONKEY_SERGAL, SPECIES_MONKEY_NEVREAN,
)
+
/datum/species/shapeshifter/holosphere/proc/try_exit_recharge_station()
var/obj/machinery/recharge_station/shell_station = holosphere_shell.loc
var/obj/machinery/recharge_station/hologram_station = holosphere_shell.hologram.loc
diff --git a/code/modules/species/holosphere/holosphere_shell.dm b/code/modules/species/holosphere/holosphere_shell.dm
index a1b7cd847dd..5190eb445ea 100644
--- a/code/modules/species/holosphere/holosphere_shell.dm
+++ b/code/modules/species/holosphere/holosphere_shell.dm
@@ -166,6 +166,7 @@
hologram.adjust_nutrition(got)
return (got * SYNTHETIC_NUTRITION_KJ_PER_UNIT) / GLOB.cellrate / SYNTHETIC_NUTRITION_INDUCER_CHEAT_FACTOR
+// support the shell having accents (hissing) if the hologram has it
/mob/living/simple_mob/holosphere_shell/proc/handle_hologram_shell_speech(datum/source, list/message_args)
var/message = message_args["message"]
var/language_name = message_args["language_name"]
diff --git a/code/modules/species/holosphere/transform.dm b/code/modules/species/holosphere/transform.dm
new file mode 100644
index 00000000000..574a61d20b6
--- /dev/null
+++ b/code/modules/species/holosphere/transform.dm
@@ -0,0 +1,41 @@
+/datum/species/shapeshifter/holosphere/proc/try_transform(force = FALSE)
+ if(!force && holosphere_shell.hologram.incapacitated(INCAPACITATION_ALL))
+ to_chat(holosphere_shell.hologram, SPAN_WARNING("You can't do that right now!"))
+ return
+ if(force || !IS_DEAD(holosphere_shell))
+ holosphere_shell.name = holosphere_shell.hologram.name
+ try_exit_recharge_station()
+ if(transform_component.try_transform())
+ holosphere_shell.hologram.drop_held_items()
+ holosphere_shell.regenerate_icons()
+
+/datum/species/shapeshifter/holosphere/proc/try_untransform(force = FALSE)
+ if(force || (!IS_DEAD(holosphere_shell.hologram) && !holosphere_shell.hologram.is_in_critical()))
+ try_exit_recharge_station()
+ transform_component.try_untransform()
+
+/datum/species/shapeshifter/holosphere/proc/get_current_transform_state()
+ return transform_component.get_current_transform_state()
+
+/datum/species/shapeshifter/holosphere/proc/get_expected_transform_state(var/current_transform_state)
+ // if hologram is in critical, you should be in your shell
+ if(holosphere_shell.hologram.is_in_critical())
+ return STATE_TRANSFORMED
+ // if hologram or shell is dead, you should be in your shell
+ if(IS_DEAD(holosphere_shell) || IS_DEAD(holosphere_shell.hologram))
+ return STATE_TRANSFORMED
+ // otherwise we can be our current state
+ return current_transform_state
+
+// compares current and expected state
+// transforms/untransforms to move to the expected state
+/datum/species/shapeshifter/holosphere/proc/handle_transform_state()
+ var/current_transform_state = get_current_transform_state()
+ var/expected_transform_state = get_expected_transform_state(current_transform_state)
+ if(current_transform_state == expected_transform_state)
+ return
+
+ if(expected_transform_state == STATE_TRANSFORMED)
+ try_transform(force = TRUE) // transform even if dead
+ else if(expected_transform_state == STATE_NOT_TRANSFORMED)
+ try_untransform(force = TRUE) // untransform even if dead
diff --git a/code/modules/species/holosphere/verbs.dm b/code/modules/species/holosphere/verbs.dm
new file mode 100644
index 00000000000..432bf291992
--- /dev/null
+++ b/code/modules/species/holosphere/verbs.dm
@@ -0,0 +1,10 @@
+/mob/living/carbon/human/proc/disable_hologram()
+ set name = "Disable Hologram (Holosphere)"
+ set desc = "Disable your hologram."
+ set category = VERB_CATEGORY_IC
+
+ var/datum/species/shapeshifter/holosphere/holosphere_species = species
+ if(!istype(holosphere_species))
+ return
+
+ holosphere_species.try_transform()