Skin Masquerade: Epidermal Applicator, Lifelike Quirk, and the Skinmonger Implant (#31185)

* adds the epidermal applicator, lifelike quirk, and skinmonger traitor implant

* lint

* lint

* attack chain fix

* more linty fixes

* more lint

* fix some buggy bugs, synthetic skinned body parts recolor to their host

* make emp on skinmonger work, fix a skinmonger bug

* make bruising / denting examine text not betray the masquerade

* burn away synthetic skin with acid

* comment / code structure / description tweaks

* i sell pharmaceuticals and pharmaceutical accessories

* code review comments

* oops

* god damn it

* god damn it (x2)

* Update code/modules/surgery/organs/augments_internal.dm

Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>

* lower syndi level for implant

* Update code/modules/research/designs/medical_designs.dm

Co-authored-by: JimKil3 <47290811+JimKil3@users.noreply.github.com>
Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>

* Update code/game/objects/items/tools/epidermal_applicator.dm

Co-authored-by: JimKil3 <47290811+JimKil3@users.noreply.github.com>
Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>

* Update code/modules/surgery/organs/organ_external.dm

Co-authored-by: JimKil3 <47290811+JimKil3@users.noreply.github.com>
Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>

* Update code/modules/surgery/organs/organ_external.dm

Co-authored-by: JimKil3 <47290811+JimKil3@users.noreply.github.com>
Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>

* tweaks for review

* Update code/__DEFINES/dcs/datum_signals.dm

Co-authored-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>

---------

Signed-off-by: Pooble <90473506+poobsie@users.noreply.github.com>
Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Co-authored-by: JimKil3 <47290811+JimKil3@users.noreply.github.com>
Co-authored-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
This commit is contained in:
Pooble
2026-03-22 18:08:25 -04:00
committed by GitHub
parent 11486a2245
commit aaeb5fdf28
18 changed files with 679 additions and 8 deletions
@@ -1094,6 +1094,182 @@
REMOVE_TRAIT(M, TRAIT_IPC_CAN_EAT, "ipc_food[UID()]")
return ..()
/obj/item/organ/internal/cyberimp/chest/skinmonger
name = "\improper Skinmonger"
desc = "A strange implant that continuously produces and replaces synthetic skin. \
Will not apply skin to a monitor-shaped head."
implant_color = "#FFDDAA"
slot = "chest_synthetic_skin"
w_class = WEIGHT_CLASS_TINY
origin_tech = "materials=6;biotech=7;syndicate=1"
augment_icon = "nutripump"
/// Whether regeneration is allowed. This is disabled temporarily by an EMP
var/regeneration_active = TRUE
/// How long to wait after finding a target body part to replace skin on
var/regen_cooldown = 30 SECONDS
/// Whether we're currently regenerating
var/regenerating = FALSE
/// Normally synthetic skin doesn't have memory of the identity we're disguised as. This implant does, though
var/configured_identity = "Unknown"
/// Flag for tracking if we've used up the implant's initial deployment of skin
var/initial_surge_used = FALSE
/obj/item/organ/internal/cyberimp/chest/skinmonger/insert(mob/living/carbon/M, special = 0)
. = ..()
if(!initial_surge_used)
apply_full_synthetic_skin(M)
to_chat(M, SPAN_WARNING("In an instant, a surge of skin wraps around you and binds itself to your body!"))
playsound(M, 'sound/surgery/organ2.ogg', 70, TRUE, frequency = 0.8)
initial_surge_used = TRUE
/obj/item/organ/internal/cyberimp/chest/skinmonger/remove(mob/living/carbon/M, special = 0)
. = ..()
regenerating = FALSE
remove_all_synthetic_skin(M)
// Called when synthetic skin is removed from any part - starts regeneration cycle
/obj/item/organ/internal/cyberimp/chest/skinmonger/proc/start_regeneration()
if(!regeneration_active || regenerating)
return
regenerating = TRUE
// Wait before we trigger the first regen.
addtimer(CALLBACK(src, PROC_REF(regenerate_next_part)), regen_cooldown)
/obj/item/organ/internal/cyberimp/chest/skinmonger/proc/regenerate_next_part()
if(!owner || !regeneration_active || !regenerating)
return
var/mob/living/carbon/human/H = owner
var/list/unskinned_parts = list()
// Find all robotic parts without synthetic skin.
for(var/obj/item/organ/external/E in H.bodyparts)
if(!E.is_robotic() || E.has_synthetic_skin)
continue
// Skip monitor heads
if(E.limb_name == "head" && E.model)
var/datum/robolimb/R = GLOB.all_robolimbs[E.model]
if(R && R.is_monitor)
continue
unskinned_parts += E
// If no parts need skin, kill the regen cycle
if(!length(unskinned_parts))
regenerating = FALSE
return
// Pick a random part and apply skin
var/obj/item/organ/external/chosen_part = pick(unskinned_parts)
chosen_part.has_synthetic_skin = TRUE
// Apply owner's skin color to synthetic skin
if(ishuman(H))
chosen_part.synthetic_skin_colour = H.skin_colour
// Set identity for head regeneration
if(chosen_part.limb_name == "head")
var/identity_to_use = configured_identity
chosen_part.synthetic_skin_identity = identity_to_use
if(ishuman(H))
H.real_name = identity_to_use
// Chest and lower body tied together for regeneration
if(chosen_part.limb_name == "chest")
var/obj/item/organ/external/groin_limb = H.bodyparts_by_name["groin"]
if(groin_limb && groin_limb.is_robotic() && !groin_limb.has_synthetic_skin)
groin_limb.has_synthetic_skin = TRUE
groin_limb.synthetic_skin_colour = H.skin_colour
groin_limb.force_icon = null
groin_limb.mob_icon = null
groin_limb.compile_icon()
if(chosen_part.limb_name == "groin")
var/obj/item/organ/external/chest_limb = H.bodyparts_by_name["chest"]
if(chest_limb && chest_limb.is_robotic() && !chest_limb.has_synthetic_skin)
chest_limb.has_synthetic_skin = TRUE
chest_limb.synthetic_skin_colour = H.skin_colour
chest_limb.force_icon = null
chest_limb.mob_icon = null
chest_limb.compile_icon()
// Refresh sprite
chosen_part.force_icon = null
chosen_part.mob_icon = null
chosen_part.compile_icon()
H.update_body(rebuild_base = TRUE)
to_chat(H, SPAN_NOTICE("You feel a wave of synthetic skin gush forth and bind to your [chosen_part.name]."))
// Schedule next regeneration if more parts need skin. Otherwise, we're done
if(length(unskinned_parts) > 1)
addtimer(CALLBACK(src, PROC_REF(regenerate_next_part)), regen_cooldown)
else
regenerating = FALSE
// One-time application of skin across the entire chassis when the implant is inserted.
/obj/item/organ/internal/cyberimp/chest/skinmonger/proc/apply_full_synthetic_skin(mob/living/carbon/human/target)
if(!ishuman(target))
return
var/mob/living/carbon/human/H = target
var/head_skinned = FALSE
for(var/obj/item/organ/external/E in H.bodyparts)
if(!E.is_robotic() || E.has_synthetic_skin)
continue
// Skip monitor heads
if(E.limb_name == "head" && E.model)
var/datum/robolimb/R = GLOB.all_robolimbs[E.model]
if(R && R.is_monitor)
continue
E.has_synthetic_skin = TRUE
// Apply owner's skin color to synthetic skin
E.synthetic_skin_colour = H.skin_colour
// Set configured identity for head
if(E.limb_name == "head")
E.synthetic_skin_identity = configured_identity
head_skinned = TRUE
// Clear sprite cache
E.force_icon = null
E.mob_icon = null
E.compile_icon()
// Apply facial identity if head was skinned
if(head_skinned)
H.real_name = configured_identity
H.update_body(rebuild_base = TRUE)
/obj/item/organ/internal/cyberimp/chest/skinmonger/proc/remove_all_synthetic_skin(mob/living/carbon/human/target)
if(!ishuman(target))
return
var/mob/living/carbon/human/H = target
for(var/obj/item/organ/external/E in H.bodyparts)
if(E.is_robotic() && E.has_synthetic_skin)
E.remove_synthetic_skin(silent = TRUE) // silent because it spams the user otherwise
to_chat(H, SPAN_WARNING("You feel your synthetic skin melt away."))
H.update_body(rebuild_base = TRUE)
// EMP wipes all synthetic skin and puts the implant on cooldown
/obj/item/organ/internal/cyberimp/chest/skinmonger/emp_act(severity)
. = ..()
regenerating = FALSE
regeneration_active = FALSE
remove_all_synthetic_skin(owner)
addtimer(CALLBACK(src, PROC_REF(restart_regeneration)), regen_cooldown)
/obj/item/organ/internal/cyberimp/chest/skinmonger/proc/restart_regeneration()
if(owner)
regeneration_active = TRUE
start_regeneration()
//BOX O' IMPLANTS
/obj/item/storage/box/cyber_implants
@@ -190,4 +190,44 @@
/obj/item/autosurgeon/organ/syndicate/oneuse/syndie_mantis/l
starting_organ = /obj/item/organ/internal/cyberimp/arm/syndie_mantis/l
/obj/item/autosurgeon/organ/syndicate/oneuse/skinmonger
starting_organ = /obj/item/organ/internal/cyberimp/chest/skinmonger
/obj/item/autosurgeon/organ/syndicate/oneuse/skinmonger/attack_self__legacy__attackchain(mob/user)
if(!storedorgan)
return ..()
// Configure identity before implantation...
var/obj/item/organ/internal/cyberimp/chest/skinmonger/implant = storedorgan
// Check if they have a monitor head
var/has_monitor_head = FALSE
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/head/head_organ = H.bodyparts_by_name["head"]
if(head_organ?.model)
var/datum/robolimb/R = GLOB.all_robolimbs[head_organ.model]
if(R?.is_monitor)
has_monitor_head = TRUE
to_chat(user, SPAN_WARNING("The Skinmonger is confused by your freakishly large head, but attempts to disguise you anyway."))
// Only ask for identity if they're a machine person and don't have a monitor head
if(ismachineperson(user) && !has_monitor_head)
if(!implant.configured_identity || implant.configured_identity == "Unknown")
var/chosen_name = tgui_input_text(user, "The Skinmonger generously offers to turn you into someone else. But who?", default = "Unknown", max_length = MAX_NAME_LEN)
if(!chosen_name)
return
// Allow to appear as "Unknown" (default value)
if(chosen_name != "Unknown")
chosen_name = reject_bad_name(chosen_name, max_length = MAX_NAME_LEN)
if(!chosen_name)
to_chat(user, SPAN_WARNING("The implanter quietly hisses, rejecting your choice."))
return
implant.configured_identity = chosen_name
// ... then implant
return ..()
#undef INFINITE
+80 -1
View File
@@ -64,6 +64,12 @@
var/fragile = FALSE
///The level of false skin used to cover robotic organs on the limb. Updated when too damaged, when installed, or when an organ with it is installed.
var/augmented_skin_cover_level = 0
/// Whether this robotic limb has synthetic skin applied
var/has_synthetic_skin = FALSE
/// Stored facial identity for synthetic skin (head only)
var/synthetic_skin_identity = null
/// Stored skin color for synthetic skin
var/synthetic_skin_colour = null
// When the limb is not on a person, make sure it faces south so it's always visible.
/obj/item/organ/external/setDir()
@@ -317,6 +323,10 @@
droplimb(1) //Clean loss, just drop the limb and be done
var/mob/living/carbon/owner_old = owner //Need to update health, but need a reference in case the below check cuts off a limb.
// Check if synthetic skin should be removed from damage
check_synthetic_skin_damage()
//If limb took enough damage, try to cut or tear it off
if(owner)
if(sharp && !(limb_flags & CANNOT_DISMEMBER))
@@ -578,7 +588,68 @@ Note that amputating the affected organ does in fact remove the infection from t
if((brute_dam >= max_damage * (augmented_skin_cover_level / 3) && brute) || (burn_dam >= max_damage * (augmented_skin_cover_level / 3) && burn)) // 66% for level 2, 100% for level 3
break_augmented_skin()
// new damage icon system
/obj/item/organ/external/proc/check_synthetic_skin_damage()
if(!has_synthetic_skin || !is_robotic())
return
var/damage_percentage = (brute_dam + burn_dam) / max_damage
if(damage_percentage >= 0.8)
remove_synthetic_skin()
// Upper body and lower body are tied together
check_connected_limb_skin_damage()
// Chest and lower body are tied together with synthetic skin.
/obj/item/organ/external/proc/check_connected_limb_skin_damage()
if(!owner || !has_synthetic_skin)
return
if(limb_name == "chest")
var/damage_percentage = (brute_dam + burn_dam) / max_damage
if(damage_percentage >= 0.8)
var/obj/item/organ/external/groin_limb = owner.bodyparts_by_name["groin"]
if(groin_limb?.has_synthetic_skin)
groin_limb.remove_synthetic_skin()
if(limb_name == "groin")
var/damage_percentage = (brute_dam + burn_dam) / max_damage
if(damage_percentage >= 0.8)
var/obj/item/organ/external/chest_limb = owner.bodyparts_by_name["chest"]
if(chest_limb?.has_synthetic_skin)
chest_limb.remove_synthetic_skin()
/obj/item/organ/external/proc/remove_synthetic_skin(silent = FALSE)
if(!has_synthetic_skin)
return
has_synthetic_skin = FALSE
// Restore original identity if this is a head
if(limb_name == "head" && owner && ishuman(owner))
var/mob/living/carbon/human/H = owner
synthetic_skin_identity = null
H.real_name = H.dna.real_name
// Restore original robotic appearance
if(model)
var/datum/robolimb/R = GLOB.all_robolimbs[model]
if(R)
force_icon = R.icon
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(!silent)
to_chat(H, SPAN_WARNING("The synthetic skin on your [name] is destroyed!"))
// Force the sprite to update
mob_icon = null
compile_icon()
H.update_body(rebuild_base = TRUE)
H.UpdateDamageIcon()
var/obj/item/organ/internal/cyberimp/chest/skinmonger/regen = H.get_int_organ(/obj/item/organ/internal/cyberimp/chest/skinmonger)
if(regen)
regen.start_regeneration()
// returns just the brute/burn damage code
/obj/item/organ/external/proc/damage_state_text()
var/tburn = 0
@@ -627,6 +698,14 @@ Note that amputating the affected organ does in fact remove the infection from t
if(limb_flags & CANNOT_DISMEMBER || !owner)
return
// Loose limbs should not have any synthetic skin
if(has_synthetic_skin)
remove_synthetic_skin()
if(children)
for(var/obj/item/organ/external/child in children)
if(child.has_synthetic_skin)
child.remove_synthetic_skin()
if(HAS_TRAIT(owner, TRAIT_I_WANT_BRAINS) && !clean)
fracture()
return
+8 -2
View File
@@ -23,6 +23,10 @@
/obj/item/organ/external/proc/sync_colour_to_human(mob/living/carbon/human/H)
if(is_robotic() && !istype(dna.species, /datum/species/machine)) //machine people get skin color
// For robotic parts with synthetic skin, use stored synthetic skin color
if(has_synthetic_skin && synthetic_skin_colour)
s_col = synthetic_skin_colour
s_tone = null
return
if(dna.species && H.dna.species && dna.species.name != H.dna.species.name)
return
@@ -67,7 +71,7 @@
var/icon_file = new_icons[1]
var/new_icon_state = new_icons[2]
mob_icon = new /icon(icon_file, new_icon_state)
if(!skeletal && !is_robotic())
if(!skeletal && (!is_robotic() || (is_robotic() && has_synthetic_skin)))
if(status & ORGAN_DEAD)
mob_icon.ColorTone(COLORTONE_DEAD_EXT_ORGAN)
mob_icon.SetIntensity(0.7)
@@ -169,8 +173,10 @@
if(skeletal)
icon_file = 'icons/mob/human_races/r_skeleton.dmi'
else if(is_robotic())
else if(is_robotic() && !has_synthetic_skin)
icon_file = 'icons/mob/human_races/robotic.dmi'
else if(has_synthetic_skin && dna.species && istype(dna.species, /datum/species/machine))
icon_file = 'icons/mob/human_races/r_human.dmi'
else
// Congratulations, you are normal
icon_file = icobase