From 4b255c16e43329a92e5c9e1ce56d0e930a4a234c Mon Sep 17 00:00:00 2001
From: Luc <89928798+lewcc@users.noreply.github.com>
Date: Thu, 5 Jan 2023 07:52:34 -1000
Subject: [PATCH] Prevents limb regeneration when changing species (#19945)
* Prevents clings from quick regenerating bodyparts
* Clarify a comment
* Reworks to work with monkeys and other sources as well
* qdel organ if it doesn't get inserted
* Use initial instead of creating and qdeling
* Revert unnecessary cast
---
code/datums/diseases/kingstons.dm | 4 +-
code/game/dna/dna2.dm | 2 +-
code/game/dna/mutations/monkey.dm | 4 +-
.../gamemodes/miniantags/abduction/gland.dm | 2 +-
.../changeling/powers/humanform.dm | 2 +-
code/modules/mob/living/carbon/human/human.dm | 27 +++++++++++--
.../living/carbon/human/species/_species.dm | 39 ++++++++++++++-----
code/modules/mob/transform_procs.dm | 2 +-
8 files changed, 61 insertions(+), 21 deletions(-)
diff --git a/code/datums/diseases/kingstons.dm b/code/datums/diseases/kingstons.dm
index 1ed17e0df8f..5783b2f664b 100644
--- a/code/datums/diseases/kingstons.dm
+++ b/code/datums/diseases/kingstons.dm
@@ -43,7 +43,7 @@
affected_mob.visible_message("[affected_mob]'s form contorts into something more feline!", \
"YOU TURN INTO A TAJARAN!")
var/mob/living/carbon/human/catface = affected_mob
- catface.set_species(/datum/species/tajaran, retain_damage = TRUE)
+ catface.set_species(/datum/species/tajaran, retain_damage = TRUE, keep_missing_bodyparts = TRUE)
/datum/disease/kingstons_advanced //this used to be directly a subtype of kingstons, which sounds nice, but it ment that it would *turn you into a tarjaran always and have normal kingstons stage act* Don't make virusus subtypes unless the base virus does nothing.
@@ -90,7 +90,7 @@
if(!istype(twisted.dna.species, chosentype))
twisted.visible_message("[twisted]'s skin splits and form contorts!", \
"Your body mutates into a [initial(chosentype.name)]!")
- twisted.set_species(chosentype, retain_damage = TRUE)
+ twisted.set_species(chosentype, retain_damage = TRUE, keep_missing_bodyparts = TRUE)
else
twisted.visible_message("[twisted] scratches at their skin!", \
"You scratch your skin to try not to itch!")
diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm
index 81105a4c2a9..303f09acac7 100644
--- a/code/game/dna/dna2.dm
+++ b/code/game/dna/dna2.dm
@@ -445,7 +445,7 @@ GLOBAL_LIST_EMPTY(bad_blocks)
return
// We manually set the species to ensure all proper species change procs are called.
- destination.set_species(species.type, retain_damage = TRUE)
+ destination.set_species(species.type, retain_damage = TRUE, keep_missing_bodyparts = TRUE)
var/datum/dna/new_dna = Clone()
new_dna.species = destination.dna.species
destination.dna = new_dna
diff --git a/code/game/dna/mutations/monkey.dm b/code/game/dna/mutations/monkey.dm
index facacbe0d3f..c51522824d3 100644
--- a/code/game/dna/mutations/monkey.dm
+++ b/code/game/dna/mutations/monkey.dm
@@ -26,7 +26,7 @@
H.invisibility = 101
var/has_primitive_form = H.dna.species.primitive_form // cache this
if(has_primitive_form)
- H.set_species(has_primitive_form)
+ H.set_species(has_primitive_form, keep_missing_bodyparts = TRUE)
new /obj/effect/temp_visual/monkeyify(H.loc)
sleep(22)
@@ -60,7 +60,7 @@
H.invisibility = 101
var/has_greater_form = H.dna.species.greater_form //cache this
if(has_greater_form)
- H.set_species(has_greater_form)
+ H.set_species(has_greater_form, keep_missing_bodyparts = TRUE)
new /obj/effect/temp_visual/monkeyify/humanify(H.loc)
sleep(22)
diff --git a/code/game/gamemodes/miniantags/abduction/gland.dm b/code/game/gamemodes/miniantags/abduction/gland.dm
index 1f6b8eb52ab..397d199dbb8 100644
--- a/code/game/gamemodes/miniantags/abduction/gland.dm
+++ b/code/game/gamemodes/miniantags/abduction/gland.dm
@@ -179,7 +179,7 @@
/obj/item/organ/internal/heart/gland/pop/activate()
to_chat(owner, "You feel unlike yourself.")
var/species = pick(/datum/species/unathi, /datum/species/skrell, /datum/species/diona, /datum/species/tajaran, /datum/species/vulpkanin, /datum/species/kidan, /datum/species/grey)
- owner.set_species(species)
+ owner.set_species(species, keep_missing_bodyparts = TRUE)
/obj/item/organ/internal/heart/gland/ventcrawling
origin_tech = "materials=4;biotech=5;bluespace=4;abductor=3"
diff --git a/code/modules/antagonists/changeling/powers/humanform.dm b/code/modules/antagonists/changeling/powers/humanform.dm
index 418592d239c..9e3d7be73be 100644
--- a/code/modules/antagonists/changeling/powers/humanform.dm
+++ b/code/modules/antagonists/changeling/powers/humanform.dm
@@ -14,7 +14,7 @@
user.dna.SetSEState(GLOB.monkeyblock,0,1)
singlemutcheck(user,GLOB.monkeyblock, MUTCHK_FORCED)
if(istype(user))
- user.set_species(chosen_dna.species.type)
+ user.set_species(chosen_dna.species.type, keep_missing_bodyparts = TRUE)
user.dna = chosen_dna.Clone()
user.real_name = chosen_dna.real_name
domutcheck(user, MUTCHK_FORCED)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 04fed84f41f..03ed3fbf733 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1243,7 +1243,7 @@
/mob/living/carbon/human/proc/change_dna(datum/dna/new_dna, include_species_change = FALSE, keep_flavor_text = FALSE)
if(include_species_change)
- set_species(new_dna.species.type, retain_damage = TRUE, transformation = TRUE)
+ set_species(new_dna.species.type, retain_damage = TRUE, transformation = TRUE, keep_missing_bodyparts = TRUE)
dna = new_dna.Clone()
if (include_species_change) //We have to call this after new_dna.Clone() so that species actions don't get overwritten
dna.species.on_species_gain(src)
@@ -1258,7 +1258,19 @@
sec_hud_set_ID()
-/mob/living/carbon/human/proc/set_species(datum/species/new_species, use_default_color = FALSE, delay_icon_update = FALSE, skip_same_check = FALSE, retain_damage = FALSE, transformation = FALSE)
+/**
+ * Change a mob's species.
+ *
+ * Arguments:
+ * * new_species - The user's new species.
+ * * use_default_color - If true, use the species' default color for the new mob.
+ * * delay_icon_update - If true, UpdateAppearance() won't be called in this proc.
+ * * skip_same_check - If true, don't bail out early if we would be changing to our current species and run through everything anyway.
+ * * retain_damage - If true, damage on the mob will be re-applied post-transform. Otherwise, the mob will have its organs healed.
+ * * transformation - If true, don't apply new species traits to the mob. A false value should be used when creating a new mob instead of transforming into a new species.
+ * * keep_missing_bodyparts - If true, any bodyparts (legs, head, etc.) that were missing on the mob before species change will be missing post-change as well.
+ */
+/mob/living/carbon/human/proc/set_species(datum/species/new_species, use_default_color = FALSE, delay_icon_update = FALSE, skip_same_check = FALSE, retain_damage = FALSE, transformation = FALSE, keep_missing_bodyparts = FALSE)
if(!skip_same_check)
if(dna.species.name == initial(new_species.name))
return
@@ -1316,9 +1328,16 @@
if(!transformation) //Distinguish between creating a mob and switching species
dna.species.on_species_gain(src)
+ var/list/missing_bodyparts = list() // should line up here to pop out only what's missing
+ if(keep_missing_bodyparts)
+ for(var/organ_name as anything in bodyparts_by_name)
+ if(isnull(bodyparts_by_name[organ_name]))
+ missing_bodyparts |= organ_name
+
if(retain_damage)
//Create a list of body parts which are damaged by burn or brute and save them to apply after new organs are generated. First we just handle external organs.
var/bodypart_damages = list()
+
//Loop through all external organs and save the damage states for brute and burn
for(var/obj/item/organ/external/E as anything in bodyparts)
if(E.brute_dam == 0 && E.burn_dam == 0 && !(E.status & ORGAN_INT_BLEEDING)) //If there's no damage we don't bother remembering it.
@@ -1342,7 +1361,7 @@
internal_damages += list(stats)
//Create the new organs for the species change
- dna.species.create_organs(src)
+ dna.species.create_organs(src, missing_bodyparts)
//Apply relevant damages and variables to the new organs.
for(var/obj/item/organ/external/E as anything in bodyparts)
@@ -1377,7 +1396,7 @@
qdel(part)
else
- dna.species.create_organs(src)
+ dna.species.create_organs(src, missing_bodyparts)
for(var/obj/item/thing in kept_items)
var/equipped = equip_to_slot_if_possible(thing, kept_items[thing])
diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm
index 5808f42bfca..3e931181150 100644
--- a/code/modules/mob/living/carbon/human/species/_species.dm
+++ b/code/modules/mob/living/carbon/human/species/_species.dm
@@ -190,7 +190,15 @@
var/datum/language/species_language = GLOB.all_languages[language]
return species_language.get_random_name(gender)
-/datum/species/proc/create_organs(mob/living/carbon/human/H) //Handles creation of mob organs.
+
+/**
+ * Handles creation of mob organs.
+ *
+ * Arguments:
+ * * H: The human to create organs inside of
+ * * bodyparts_to_omit: Any bodyparts in this list (and organs within them) should not be added.
+ */
+/datum/species/proc/create_organs(mob/living/carbon/human/H, list/bodyparts_to_omit) //Handles creation of mob organs.
QDEL_LIST_CONTENTS(H.internal_organs)
QDEL_LIST_CONTENTS(H.bodyparts)
@@ -198,21 +206,29 @@
LAZYREINITLIST(H.bodyparts_by_name)
LAZYREINITLIST(H.internal_organs)
- for(var/limb_type in has_limbs)
- var/list/organ_data = has_limbs[limb_type]
+ for(var/limb_name in has_limbs)
+ if(bodyparts_to_omit && (limb_name in bodyparts_to_omit))
+ H.bodyparts_by_name[limb_name] = null // Null it out, but leave the name here so it's still "there"
+ continue
+ var/list/organ_data = has_limbs[limb_name]
var/limb_path = organ_data["path"]
var/obj/item/organ/O = new limb_path(H)
organ_data["descriptor"] = O.name
for(var/index in has_organ)
- var/organ = has_organ[index]
- // organ new code calls `insert` on its own
- new organ(H)
+ var/obj/item/organ/internal/organ_path = has_organ[index]
+ if(initial(organ_path.parent_organ) in bodyparts_to_omit)
+ continue
+
+ var/obj/item/organ/internal/org = new organ_path()
+ org.insert(H)
create_mutant_organs(H)
for(var/name in H.bodyparts_by_name)
- H.bodyparts |= H.bodyparts_by_name[name]
+ var/part_type = H.bodyparts_by_name[name]
+ if(!isnull(part_type))
+ H.bodyparts |= part_type // we do not want nulls here, even though it's alright to have them in bodyparts_by_name
for(var/obj/item/organ/external/E as anything in H.bodyparts)
E.owner = H
@@ -221,10 +237,15 @@
/datum/species/proc/create_mutant_organs(mob/living/carbon/human/H)
var/obj/item/organ/internal/ears/ears = H.get_int_organ(/obj/item/organ/internal/ears)
if(ears)
+ if(istype(ears, mutantears))
+ // if they're the same, just heal them and don't bother replacing them
+ ears.rejuvenate()
+ return
qdel(ears)
- if(mutantears)
- ears = new mutantears(H)
+ if(mutantears && !isnull(H.bodyparts_by_name[initial(mutantears.parent_organ)]))
+ var/obj/item/organ/internal/ears/new_ears = new mutantears()
+ new_ears.insert(H)
/datum/species/proc/breathe(mob/living/carbon/human/H)
if(HAS_TRAIT(H, TRAIT_NOBREATH))
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index b8a2b492ed2..d3257eff6b5 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -1,6 +1,6 @@
/mob/living/carbon/human/proc/monkeyize()
var/mob/H = src
- H.dna.SetSEState(GLOB.monkeyblock,1)
+ H.dna.SetSEState(GLOB.monkeyblock, 1)
singlemutcheck(H, GLOB.monkeyblock, MUTCHK_FORCED)
/mob/new_player/AIize()