mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-22 03:27:05 +01:00
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:
co-authored by
1080pCat
JimKil3
PollardTheDragon
parent
11486a2245
commit
aaeb5fdf28
@@ -29,6 +29,8 @@
|
||||
#define COMSIG_BODY_TRANSFER_TO "body_transfer_to"
|
||||
///called when the mind is initialized (called every time the mob logins)
|
||||
#define COMSIG_MIND_INITIALIZE "mind_initialize"
|
||||
/// called when character creation robotic limbs are applied
|
||||
#define COMSIG_HUMAN_ROBOTIC_LIMBS_APPLIED "human_robotic_limbs_applied"
|
||||
|
||||
// Sent from a surgery step when blood is being splashed. (datum/surgery, mob/user, mob/target, zone, obj/item/tool)
|
||||
#define COMSIG_SURGERY_BLOOD_SPLASH "surgery_blood_splash"
|
||||
|
||||
@@ -663,6 +663,16 @@
|
||||
cost = 40
|
||||
excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST) //No, nukies do not get to dodge bullets.
|
||||
|
||||
/datum/uplink_item/species_restricted/skinmonger
|
||||
name = "Skinmonger Autoimplanter"
|
||||
desc = "A strange implant that continuously fabricates synthetic epidermis, covering up prosthetics. \
|
||||
When implanted, the Skinmonger will bond to its host, first covering every limb in synthetic skin, then replacing destroyed skin periodically. \
|
||||
IPCs can use this implant to disguise themselves as human. However, it will not cover monitor-shaped heads."
|
||||
reference = "SKINMON"
|
||||
item = /obj/item/autosurgeon/organ/syndicate/oneuse/skinmonger
|
||||
cost = 10
|
||||
species = list("Machine")
|
||||
|
||||
/datum/uplink_item/badass/syndiecards
|
||||
name = "Syndicate Playing Cards"
|
||||
desc = "A special deck of space-grade playing cards with a mono-molecular edge and metal reinforcement, making them lethal weapons both when wielded as a blade and when thrown. \
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
/obj/item/epidermal_applicator
|
||||
name = "epidermal applicator"
|
||||
desc = "A pen-shaped device developed by Zeng-Hu Pharmaceuticals and used to apply synthetic skin to prosthetic limbs."
|
||||
icon = 'icons/obj/surgery.dmi'
|
||||
icon_state = "skin_applicator"
|
||||
origin_tech = "biotech=5;materials=3;engineering=4"
|
||||
new_attack_chain = TRUE
|
||||
|
||||
/// Current amount of metal stored in the applicator
|
||||
var/metal_stored = 0
|
||||
/// Maximum metal that can be stored
|
||||
var/max_metal_stored = 5
|
||||
/// Metal required per application
|
||||
var/metal_per_use = 5
|
||||
|
||||
var/applying = FALSE
|
||||
|
||||
/obj/item/epidermal_applicator/Initialize(mapload)
|
||||
. = ..()
|
||||
// Start with empty storage
|
||||
metal_stored = 0
|
||||
|
||||
/obj/item/epidermal_applicator/examine(mob/user)
|
||||
. = ..()
|
||||
|
||||
if(metal_stored >= metal_per_use)
|
||||
. += SPAN_NOTICE("It is loaded and ready to apply an epidermal layer to a body part.")
|
||||
else
|
||||
. += SPAN_NOTICE("It needs [metal_per_use - metal_stored] more metal sheets.")
|
||||
|
||||
/obj/item/epidermal_applicator/proc/is_insertion_ready(mob/user)
|
||||
if(!user)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/item/epidermal_applicator/item_interaction(mob/living/user, obj/item/used, list/modifiers)
|
||||
if(!istype(used, /obj/item/stack/sheet/metal))
|
||||
return NONE
|
||||
|
||||
var/space_left = max_metal_stored - metal_stored
|
||||
if(space_left <= 0)
|
||||
to_chat(user, SPAN_NOTICE("[src] is already full!"))
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
|
||||
var/obj/item/stack/sheet/metal/M = used
|
||||
var/to_load = min(space_left, M.amount)
|
||||
M.use(to_load)
|
||||
metal_stored += to_load
|
||||
|
||||
to_chat(user, SPAN_NOTICE("You load [to_load] sheet\s of metal into [src]."))
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
|
||||
/obj/item/epidermal_applicator/activate_self(mob/user)
|
||||
. = ..()
|
||||
attack(user, user, null)
|
||||
|
||||
/obj/item/epidermal_applicator/attack(mob/living/M, mob/living/user, params)
|
||||
. = ..(M, user, params)
|
||||
if(.)
|
||||
return .
|
||||
|
||||
if(!ishuman(M))
|
||||
return
|
||||
|
||||
var/def_zone = user.zone_selected
|
||||
if(!def_zone)
|
||||
to_chat(user, SPAN_WARNING("You need to select a body part first!</span>"))
|
||||
return TRUE
|
||||
|
||||
var/mob/living/carbon/human/target = M
|
||||
var/obj/item/organ/external/affected = target.get_organ(def_zone)
|
||||
|
||||
if(!affected)
|
||||
to_chat(user, SPAN_WARNING("[target] doesn't have a [parse_zone(def_zone)]!"))
|
||||
return TRUE
|
||||
|
||||
// Show these identically so it can't be used to test whether a limb is synthetic or not.
|
||||
if(!affected.is_robotic() || affected.has_synthetic_skin)
|
||||
to_chat(user, SPAN_WARNING("The [affected.name] doesn't need skin."))
|
||||
return TRUE
|
||||
|
||||
// Do not put skin on a monitor. No.
|
||||
if(ismachineperson(target) && def_zone == BODY_ZONE_HEAD && affected.model)
|
||||
var/datum/robolimb/R = GLOB.all_robolimbs[affected.model]
|
||||
if(R && R.is_monitor)
|
||||
to_chat(user, SPAN_WARNING("The applicator fails to find purchase on your big cube head. Probably for the best."))
|
||||
return TRUE
|
||||
|
||||
// Check if we have enough metal
|
||||
if(metal_stored < metal_per_use)
|
||||
to_chat(user, SPAN_WARNING("[src] needs [metal_per_use] metal to function."))
|
||||
return TRUE
|
||||
|
||||
if(applying)
|
||||
to_chat(user, SPAN_WARNING("[src] is already in use!"))
|
||||
return TRUE
|
||||
|
||||
// Start application process
|
||||
apply_synthetic_skin(target, affected, user, def_zone)
|
||||
return TRUE
|
||||
|
||||
/obj/item/epidermal_applicator/proc/apply_synthetic_skin(mob/living/carbon/human/target, obj/item/organ/external/affected, mob/living/user, def_zone)
|
||||
applying = TRUE
|
||||
|
||||
var/chosen_identity = "Unknown" // Default identity
|
||||
|
||||
// Ask for identity choice if applying to head
|
||||
if(def_zone == BODY_ZONE_HEAD)
|
||||
var/list/identity_choices = list("Unknown")
|
||||
var/list/choice_map = list("Unknown" = "Unknown")
|
||||
|
||||
// Add real name option
|
||||
if(target.dna?.real_name)
|
||||
var/real_name_choice = "[target.dna.real_name]"
|
||||
identity_choices += real_name_choice
|
||||
choice_map[real_name_choice] = target.dna.real_name
|
||||
|
||||
// Check for offhand ID
|
||||
var/obj/item/offhand_item = user.get_inactive_hand()
|
||||
var/offhand_id_name = null
|
||||
if(istype(offhand_item, /obj/item/card/id))
|
||||
var/obj/item/card/id/id_card = offhand_item
|
||||
offhand_id_name = id_card.registered_name
|
||||
else if(is_pda(offhand_item))
|
||||
var/obj/item/pda/pda = offhand_item
|
||||
offhand_id_name = pda.owner
|
||||
|
||||
// Add ID name option if found
|
||||
if(offhand_id_name && offhand_id_name != "")
|
||||
var/id_name_choice = "[offhand_id_name]"
|
||||
identity_choices += id_name_choice
|
||||
choice_map[id_name_choice] = offhand_id_name
|
||||
|
||||
// Present choice to user
|
||||
var/chosen_identity_option = tgui_input_list(user, "Choose facial identity:", "Identity Selection", identity_choices)
|
||||
if(!chosen_identity_option)
|
||||
applying = FALSE
|
||||
return // Cancel if no choice made
|
||||
|
||||
chosen_identity = choice_map[chosen_identity_option]
|
||||
|
||||
user.visible_message(
|
||||
SPAN_NOTICE("[user] begins applying synthetic skin to [target == user ? "their" : "[target]'s"] [affected.name] with [src]."),
|
||||
SPAN_NOTICE("You begin applying synthetic skin to [target == user ? "your" : "[target]'s"] [affected.name]...")
|
||||
)
|
||||
|
||||
// Play start sound
|
||||
playsound(get_turf(src), 'sound/effects/spray.ogg', 50, TRUE)
|
||||
|
||||
// 10 second do-after
|
||||
if(do_after(user, 10 SECONDS, target = target))
|
||||
// Play end sound
|
||||
playsound(get_turf(src), 'sound/effects/spray3.ogg', 50, TRUE)
|
||||
|
||||
// Consume metal
|
||||
metal_stored -= metal_per_use
|
||||
|
||||
// Apply synthetic skin
|
||||
affected.has_synthetic_skin = TRUE
|
||||
|
||||
// Apply owner skin color to synthetic skin
|
||||
if(ishuman(target))
|
||||
affected.synthetic_skin_colour = target.skin_colour
|
||||
|
||||
// Set identity if applying to head (we already got the choice above)
|
||||
if(def_zone == BODY_ZONE_HEAD)
|
||||
affected.synthetic_skin_identity = chosen_identity
|
||||
if(ishuman(target))
|
||||
target.real_name = chosen_identity
|
||||
|
||||
// Clear force_icon so it unsticks from the robotic sprites
|
||||
affected.force_icon = null
|
||||
|
||||
// Apply to connected torso parts as well
|
||||
if(def_zone == BODY_ZONE_CHEST)
|
||||
var/obj/item/organ/external/groin_limb = target.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 = target.skin_colour
|
||||
groin_limb.force_icon = null
|
||||
groin_limb.mob_icon = null
|
||||
groin_limb.compile_icon()
|
||||
|
||||
if(def_zone == BODY_ZONE_PRECISE_GROIN)
|
||||
var/obj/item/organ/external/chest_limb = target.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 = target.skin_colour
|
||||
chest_limb.force_icon = null
|
||||
chest_limb.mob_icon = null
|
||||
chest_limb.compile_icon()
|
||||
|
||||
// Force the organ to completely regenerate its mob_icon
|
||||
affected.mob_icon = null
|
||||
affected.compile_icon()
|
||||
|
||||
// Force complete body rebuild to bypass icon cache
|
||||
target.update_body(rebuild_base = TRUE)
|
||||
target.UpdateDamageIcon()
|
||||
|
||||
user.visible_message(
|
||||
SPAN_NOTICE("[user] applies synthetic skin to [target == user ? "their" : "[target]'s"] [affected.name]."),
|
||||
SPAN_NOTICE("You apply synthetic skin to [target == user ? "your" : "[target]'s"] [affected.name].")
|
||||
)
|
||||
|
||||
if(target != user)
|
||||
to_chat(target, SPAN_NOTICE("You feel a thin layer of synthetic skin form over your [affected.name]."))
|
||||
|
||||
applying = FALSE
|
||||
@@ -1964,6 +1964,9 @@
|
||||
if(status == "cybernetic")
|
||||
I.robotize()
|
||||
|
||||
// Send signal that robotic limbs have been applied
|
||||
SEND_SIGNAL(character, COMSIG_HUMAN_ROBOTIC_LIMBS_APPLIED)
|
||||
|
||||
character.dna.blood_type = b_type
|
||||
|
||||
// Wheelchair necessary?
|
||||
|
||||
@@ -36,6 +36,50 @@
|
||||
trait_to_apply = TRAIT_GLUTTON
|
||||
species_flags = QUIRK_MACHINE_INCOMPATIBLE
|
||||
|
||||
/datum/quirk/lifelike
|
||||
name = "Lifelike"
|
||||
desc = "Your prosthetic limbs have been fitted with a synthetic epidermis, making them appear natural. \
|
||||
For IPCs, this covers all body parts, making them look human (except monitor-shaped heads). \
|
||||
For all others, it covers prosthetic limbs."
|
||||
cost = 4
|
||||
|
||||
/datum/quirk/lifelike/apply_quirk_effects(mob/living/carbon/human/target, character)
|
||||
. = ..(target, character)
|
||||
// Apply synthetic skin after robotic limbs are applied or the quirk doesn't work very well
|
||||
RegisterSignal(target, COMSIG_HUMAN_ROBOTIC_LIMBS_APPLIED, PROC_REF(apply_synthetic_skin_on_signal))
|
||||
|
||||
/datum/quirk/lifelike/proc/apply_synthetic_skin_on_signal(mob/living/carbon/human/target)
|
||||
SIGNAL_HANDLER // COMSIG_HUMAN_ROBOTIC_LIMBS_APPLIED
|
||||
|
||||
for(var/obj/item/organ/external/limb as anything in target.bodyparts)
|
||||
if(!limb)
|
||||
continue
|
||||
|
||||
// Skip monitor heads
|
||||
if(limb.limb_name == "head" && limb.model)
|
||||
var/datum/robolimb/R = GLOB.all_robolimbs[limb.model]
|
||||
if(R && R.is_monitor)
|
||||
continue
|
||||
|
||||
if(ismachineperson(target) || limb.is_robotic())
|
||||
limb.has_synthetic_skin = TRUE
|
||||
// Apply owner's skin color to synthetic skin
|
||||
limb.synthetic_skin_colour = target.skin_colour
|
||||
// Set real identity for head
|
||||
if(limb.limb_name == "head")
|
||||
limb.synthetic_skin_identity = target.dna.real_name
|
||||
// Clear cached limb icon because otherwise it's sticky
|
||||
limb.force_icon = null
|
||||
// Force mob icon regeneration
|
||||
limb.mob_icon = null
|
||||
limb.compile_icon()
|
||||
|
||||
// Now rebuild appearance
|
||||
target.update_body(rebuild_base = TRUE)
|
||||
|
||||
// Unregister the signal since we're done with it
|
||||
UnregisterSignal(target, COMSIG_HUMAN_ROBOTIC_LIMBS_APPLIED)
|
||||
|
||||
/obj/item/storage/box/papersack/prepped_meal
|
||||
name = "packed meal"
|
||||
var/list/entree_options = list(
|
||||
|
||||
@@ -409,6 +409,11 @@ emp_act
|
||||
update_hair()
|
||||
update_fhair()
|
||||
|
||||
if(affecting.has_synthetic_skin)
|
||||
visible_message(SPAN_WARNING("The synthetic skin on [src]'s [affecting.name] bubbles and melts away."), \
|
||||
SPAN_WARNING("The synthetic skin on your [affecting.name] bubbles and melts away."))
|
||||
affecting.remove_synthetic_skin(TRUE)
|
||||
|
||||
UpdateDamageIcon()
|
||||
|
||||
//MELTING INVENTORY ITEMS//
|
||||
|
||||
@@ -116,6 +116,8 @@
|
||||
var/list/quirks = list()
|
||||
/// The cooldown for jumping into a closet or crate
|
||||
COOLDOWN_DECLARE(skittish_cooldown)
|
||||
/// Cache whether or not an IPC appears human during examine to avoid needless recalculation
|
||||
var/ipc_masquerade_status
|
||||
|
||||
/mob/living/carbon/human/fake
|
||||
flags = ABSTRACT
|
||||
|
||||
@@ -53,6 +53,12 @@
|
||||
if(C.species_disguise)
|
||||
displayed_species = C.species_disguise
|
||||
break
|
||||
|
||||
// If an IPC's covered in synthetic skin, they can appear human.
|
||||
if(calculate_ipc_masquerade_status())
|
||||
displayed_species = "Human"
|
||||
examine_color = "#d1aa2e"
|
||||
|
||||
if(skip_jumpsuit && skip_face || HAS_TRAIT(src, TRAIT_NOEXAMINE)) //either obscured or on the nospecies list
|
||||
msg += "!" //omit the species when examining
|
||||
else
|
||||
@@ -122,7 +128,7 @@
|
||||
continue
|
||||
|
||||
if(!ismachineperson(src))
|
||||
if(E.is_robotic())
|
||||
if(E.is_robotic() && !E.has_synthetic_skin)
|
||||
wound_flavor_text["[E.limb_name]"] = "[p_they(TRUE)] [p_have()] a robotic [E.name]!\n"
|
||||
|
||||
else if(E.status & ORGAN_SPLINTED)
|
||||
@@ -256,5 +262,61 @@
|
||||
|
||||
return msg
|
||||
|
||||
/mob/living/carbon/human/proc/calculate_ipc_masquerade_status()
|
||||
if(!ismachineperson(src))
|
||||
return FALSE
|
||||
|
||||
var/all_visible_parts_have_skin = TRUE
|
||||
|
||||
for(var/obj/item/organ/external/limb as anything in bodyparts)
|
||||
if(!limb || !limb.is_robotic())
|
||||
continue
|
||||
|
||||
// If it's covered by clothing then it doesn't need to have skin for the masquerade
|
||||
if(is_bodypart_covered_by_clothing(limb.limb_name))
|
||||
continue
|
||||
|
||||
if(!limb.has_synthetic_skin)
|
||||
return FALSE
|
||||
|
||||
return all_visible_parts_have_skin
|
||||
|
||||
/mob/living/carbon/human/examine_get_brute_message()
|
||||
return !ismachineperson(src) ? "bruising" : "denting"
|
||||
if(!ismachineperson(src) || calculate_ipc_masquerade_status())
|
||||
return "bruising"
|
||||
|
||||
return "denting"
|
||||
|
||||
/// Checks if a body part is covered by clothing
|
||||
/mob/living/carbon/human/proc/is_bodypart_covered_by_clothing(part_name)
|
||||
var/bodypart_clothing_bitflag = bodypart_name_to_clothing_bitflag(part_name)
|
||||
if(!bodypart_clothing_bitflag)
|
||||
return FALSE
|
||||
|
||||
// Masks
|
||||
if(bodypart_clothing_bitflag & HEAD)
|
||||
var/obj/item/clothing/mask/current_mask = wear_mask
|
||||
if(istype(current_mask) && (current_mask.body_parts_covered & bodypart_clothing_bitflag))
|
||||
return TRUE
|
||||
|
||||
// Jumpsuit/uniform
|
||||
var/chest_groin_arms_legs_bitflag = ARMS | LEGS | UPPER_TORSO | LOWER_TORSO
|
||||
if(bodypart_clothing_bitflag & chest_groin_arms_legs_bitflag)
|
||||
if(w_uniform && (w_uniform.body_parts_covered & bodypart_clothing_bitflag))
|
||||
return TRUE
|
||||
if(wear_suit && (wear_suit.body_parts_covered & bodypart_clothing_bitflag))
|
||||
return TRUE
|
||||
|
||||
// Gloves
|
||||
if(bodypart_clothing_bitflag & HANDS)
|
||||
var/obj/item/clothing/gloves/current_gloves = gloves
|
||||
if(istype(current_gloves) && (current_gloves.body_parts_covered & bodypart_clothing_bitflag))
|
||||
return TRUE
|
||||
|
||||
// Shoes
|
||||
if(bodypart_clothing_bitflag & FEET)
|
||||
var/obj/item/clothing/shoes/current_shoes = shoes
|
||||
if(istype(current_shoes) && (current_shoes.body_parts_covered & bodypart_clothing_bitflag))
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
@@ -198,6 +198,8 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
|
||||
base_icon = chest.get_icon(skeleton)
|
||||
|
||||
for(var/obj/item/organ/external/part in bodyparts)
|
||||
part.sync_colour_to_human(src)
|
||||
|
||||
// We just drew the chest, don't draw it twice.
|
||||
if(part == chest)
|
||||
continue
|
||||
|
||||
@@ -348,6 +348,16 @@
|
||||
if(istype(affecting))
|
||||
affecting.disfigure()
|
||||
|
||||
var/was_skin_removed = FALSE
|
||||
for(var/obj/item/organ/external/limb in H.bodyparts)
|
||||
if(limb.has_synthetic_skin)
|
||||
was_skin_removed = TRUE
|
||||
limb.remove_synthetic_skin(TRUE)
|
||||
|
||||
if(was_skin_removed)
|
||||
H.visible_message("<span class='warning'>The synthetic skin on [H]'s body bubbles and melts away.</span>", \
|
||||
"<span class='warning'>The synthetic skin on your body bubbles and melts away.</span>")
|
||||
|
||||
/datum/reagent/acid/reaction_obj(obj/O, volume)
|
||||
if(ismob(O.loc)) //handled in human acid_act()
|
||||
return
|
||||
|
||||
@@ -71,6 +71,16 @@
|
||||
build_path = /obj/item/reagent_containers/applicator
|
||||
category = list("Medical")
|
||||
|
||||
/datum/design/epidermal_applicator
|
||||
name = "Epidermal Applicator"
|
||||
desc = "A pen-shaped device developed by Zeng-Hu Pharmaceuticals and used to apply synthetic skin to prosthetic limbs."
|
||||
id = "epidermal_applicator"
|
||||
req_tech = list("biotech" = 5, "materials" = 6, "engineering" = 4)
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_METAL = 2000, MAT_GLASS = 1000, MAT_SILVER = 500)
|
||||
build_path = /obj/item/epidermal_applicator
|
||||
category = list("Medical")
|
||||
|
||||
/datum/design/handheld_defib
|
||||
name = "Handheld Defibrillator"
|
||||
desc = "A smaller defibrillator only capable of treating cardiac arrest."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -787,11 +787,21 @@
|
||||
|
||||
var/new_gender = gender_list[gender_key]
|
||||
var/old_name = target.real_name
|
||||
target.real_name = new_name
|
||||
var/identity_type = "core identity parameters"
|
||||
|
||||
// Prioritize replacing a synthetic skin identity over the IPC's actual identity
|
||||
var/obj/item/organ/external/head = target.bodyparts_by_name["head"]
|
||||
if(head && head.has_synthetic_skin)
|
||||
head.synthetic_skin_identity = new_name
|
||||
target.real_name = new_name
|
||||
identity_type = "synthetic facial identity"
|
||||
else
|
||||
target.real_name = new_name
|
||||
|
||||
target.gender = new_gender
|
||||
user.visible_message(
|
||||
SPAN_NOTICE("[user] edits [old_name]'s identity parameters with [tool]; [target.p_they()] [target.p_are()] now known as [new_name]."),
|
||||
SPAN_NOTICE("You alter [old_name]'s identity parameters with [tool]; [target.p_they()] [target.p_are()] now known as [new_name]."),
|
||||
SPAN_NOTICE("[user] edits [old_name]'s [identity_type] with [tool]; [target.p_they()] [target.p_are()] now known as [new_name]."),
|
||||
SPAN_NOTICE("You alter [old_name]'s [identity_type] with [tool]; [target.p_they()] [target.p_are()] now known as [new_name]."),
|
||||
chat_message_type = MESSAGE_TYPE_COMBAT
|
||||
)
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
@@ -1445,6 +1445,7 @@
|
||||
#include "code\game\objects\items\tanks\tank_types.dm"
|
||||
#include "code\game\objects\items\tanks\watertank.dm"
|
||||
#include "code\game\objects\items\tools\crowbar.dm"
|
||||
#include "code\game\objects\items\tools\epidermal_applicator.dm"
|
||||
#include "code\game\objects\items\tools\hammer.dm"
|
||||
#include "code\game\objects\items\tools\multitool.dm"
|
||||
#include "code\game\objects\items\tools\screwdriver.dm"
|
||||
|
||||
Reference in New Issue
Block a user