From 64cf28cc4f2e91ce2f4c8acc30ccba1570485c09 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 17 Mar 2025 19:09:55 +0100 Subject: [PATCH] Adds bodypart visuals for different implants, improves eye color/blinking handling (#90010) ## About The Pull Request Added visual overlays for all arm implants, HUD implants (not the headrev one), internal thrusters, breathing tube, nutriment pumps and reviver implant. ![dreamseeker_wd79oYLszL](https://github.com/user-attachments/assets/76582c23-8639-4261-8414-622a0419dc5b) ![dreamseeker_MnlpCHD0nQ](https://github.com/user-attachments/assets/c4692105-0435-401b-aa30-66a33a813fc4) ![dreamseeker_nLJrSPGC63](https://github.com/user-attachments/assets/5b43a1a6-45d6-454e-9348-c119db3cfb43) Additionally, added a wrapper for eye color setting which solves the issue where non-pref sourced eyecolors got reset after changing them, and changed how blinking works so now update_body calls don't force you to blink. ## Why It's Good For The Game Gives you incredible drip (which makes augments feel more impactful) and allows others to see if you have certain important augments (reviver) that could matter. As for technical changes, both were required for this to HUDs to work nicely and fix some bugs as a side effect. ## Changelog :cl: add: Certain implants now have visuals when implanted fix: You no longer blink when you adjust your clothing fix: Fixed an issue where some NPC/midround humans would not get their eye color set correctly /:cl: --- code/__DEFINES/colors.dm | 5 +- code/datums/dna.dm | 3 +- code/game/objects/structures/mirror.dm | 3 +- code/modules/admin/create_mob.dm | 6 +- code/modules/antagonists/cult/cult_other.dm | 3 +- .../antagonists/fugitive/fugitive_outfits.dm | 3 +- .../mining_loot/megafauna/ash_drake.dm | 3 +- code/modules/mob/living/carbon/human/emote.dm | 9 +- .../mob/living/carbon/human/human_helpers.dm | 14 +++ .../chemistry/reagents/other_reagents.dm | 1 + .../organs/internal/cyberimp/augments_arms.dm | 32 +++++++ .../internal/cyberimp/augments_chest.dm | 44 ++++++--- .../organs/internal/cyberimp/augments_eyes.dm | 21 ++++- .../internal/cyberimp/augments_internal.dm | 68 +++++++++++++- .../surgery/organs/internal/eyes/_eyes.dm | 87 +++++++++++++----- code/modules/unit_tests/changeling.dm | 3 +- .../misc/bodypart_overlay_augmentations.dmi | Bin 0 -> 4898 bytes .../species/misc/bodypart_overlay_simple.dmi | Bin 3811 -> 3951 bytes 18 files changed, 242 insertions(+), 63 deletions(-) create mode 100644 icons/mob/human/species/misc/bodypart_overlay_augmentations.dmi diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 59af9d98210..13c70231a5f 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -486,8 +486,9 @@ GLOBAL_LIST_INIT(heretic_path_to_color, list( #define EYE_COLOR_ATOM_COLOR_PRIORITY 2 #define EYE_COLOR_SPECIES_PRIORITY 10 #define EYE_COLOR_WEED_PRIORITY 20 -#define EYE_COLOR_LUMINESCENT_PRIORITY 30 -#define EYE_COLOR_CULT_PRIORITY 40 +#define EYE_COLOR_HUD_PRIORITY 30 +#define EYE_COLOR_LUMINESCENT_PRIORITY 40 +#define EYE_COLOR_CULT_PRIORITY 50 // Client color priorities diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 9afc1b57da8..0614dea442d 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -640,8 +640,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) ..() var/structure = dna.unique_identity skin_tone = GLOB.skin_tones[deconstruct_block(get_uni_identity_block(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] - eye_color_left = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_LEFT_BLOCK)) - eye_color_right = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_RIGHT_BLOCK)) + set_eye_color(sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_LEFT_BLOCK)), sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_RIGHT_BLOCK))) set_haircolor(sanitize_hexcolor(get_uni_identity_block(structure, DNA_HAIR_COLOR_BLOCK)), update = FALSE) set_facial_haircolor(sanitize_hexcolor(get_uni_identity_block(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)), update = FALSE) set_hair_gradient_color(sanitize_hexcolor(get_uni_identity_block(structure, DNA_HAIR_COLOR_GRADIENT_BLOCK)), update = FALSE) diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 4e6d3d5634d..268f1aa3fae 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -250,8 +250,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) var/new_eye_color = input(user, "Choose your eye color", "Eye Color", user.eye_color_left) as color|null if(isnull(new_eye_color)) return TRUE - user.eye_color_left = sanitize_hexcolor(new_eye_color) - user.eye_color_right = sanitize_hexcolor(new_eye_color) + user.set_eye_color(sanitize_hexcolor(new_eye_color)) user.dna.update_ui_block(DNA_EYE_COLOR_LEFT_BLOCK) user.dna.update_ui_block(DNA_EYE_COLOR_RIGHT_BLOCK) user.update_body() diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index 944e230acd1..ec06fba0b5d 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -22,8 +22,7 @@ human.set_facial_hairstyle(random_facial_hairstyle(human.gender), update = FALSE) human.set_haircolor("#[random_color()]", update = FALSE) human.set_facial_haircolor(human.hair_color, update = FALSE) - human.eye_color_left = random_eye_color() - human.eye_color_right = human.eye_color_left + human.set_eye_color(random_eye_color()) human.skin_tone = pick(GLOB.skin_tones) human.dna.species.randomize_active_underwear_only(human) // Needs to be called towards the end to update all the UIs just set above @@ -43,8 +42,7 @@ human.physique = human.gender human.real_name = human.generate_random_mob_name() human.name = human.get_visible_name() - human.eye_color_left = random_eye_color() - human.eye_color_right = human.eye_color_left + human.set_eye_color(random_eye_color()) human.skin_tone = pick(GLOB.skin_tones) // No underwear generation handled here var/picked_color = random_hair_color() diff --git a/code/modules/antagonists/cult/cult_other.dm b/code/modules/antagonists/cult/cult_other.dm index bbeec10ce17..0e286d75e81 100644 --- a/code/modules/antagonists/cult/cult_other.dm +++ b/code/modules/antagonists/cult/cult_other.dm @@ -8,8 +8,7 @@ r_hand = /obj/item/melee/blood_magic/stun /datum/outfit/cultist/post_equip(mob/living/carbon/human/equipped, visuals_only) - equipped.eye_color_left = BLOODCULT_EYE - equipped.eye_color_right = BLOODCULT_EYE + equipped.set_eye_color(BLOODCULT_EYE) equipped.update_body() ///Returns whether the given mob is convertable to the blood cult diff --git a/code/modules/antagonists/fugitive/fugitive_outfits.dm b/code/modules/antagonists/fugitive/fugitive_outfits.dm index 5b73476a3af..a7a45fba7a1 100644 --- a/code/modules/antagonists/fugitive/fugitive_outfits.dm +++ b/code/modules/antagonists/fugitive/fugitive_outfits.dm @@ -36,8 +36,7 @@ if(visuals_only) return equipped_on.fully_replace_character_name(null, "Waldo") - equipped_on.eye_color_left = COLOR_BLACK - equipped_on.eye_color_right = COLOR_BLACK + equipped_on.set_eye_color(COLOR_BLACK) equipped_on.gender = MALE equipped_on.skin_tone = "caucasian3" equipped_on.hairstyle = "Business Hair 3" diff --git a/code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm b/code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm index 8251ed395af..c54f2d7152f 100644 --- a/code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm +++ b/code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm @@ -183,8 +183,7 @@ "lizard_markings" = "Dark Tiger Body", "legs" = DIGITIGRADE_LEGS, ) - consumer.eye_color_left = "#FEE5A3" - consumer.eye_color_right = "#FEE5A3" + consumer.set_eye_color("#FEE5A3") consumer.set_species(/datum/species/lizard) if(2) to_chat(user, span_danger("Your flesh begins to melt! Miraculously, you seem fine otherwise.")) diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index 66a2377aedc..db0d725b85a 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -186,8 +186,8 @@ /datum/emote/living/carbon/human/blink/run_emote(mob/living/carbon/human/user, params, type_override, intentional) . = ..() - // Set to update_body until update_body_parts_head_only is fixed - user.update_body() // Refreshing instantly makes the user blink + var/obj/item/organ/eyes/eyes = user.get_organ_slot(ORGAN_SLOT_EYES) + eyes.blink() /datum/emote/living/carbon/human/blink_r key = "blink_r" @@ -204,9 +204,10 @@ /datum/emote/living/carbon/human/blink_r/run_emote(mob/user, params, type_override, intentional) . = ..() - // Set to update_body until update_body_parts_head_only is fixed + var/obj/item/organ/eyes/eyes = user.get_organ_slot(ORGAN_SLOT_EYES) for (var/i in 1 to 3) - addtimer(CALLBACK(user, TYPE_PROC_REF(/mob, update_body)), i * 0.3 SECONDS) + addtimer(CALLBACK(eyes, TYPE_PROC_REF(/obj/item/organ/eyes, blink), 0.1 SECONDS, FALSE), i * 0.2 SECONDS) + eyes.animate_eyelids(user) ///Snowflake emotes only for le epic chimp /datum/emote/living/carbon/human/monkey diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 8166612a873..91b001dff5b 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -370,3 +370,17 @@ user.visible_message(span_notice("[user] fixes some of the [message] [src]'s [affecting.name]."), \ span_notice("You fix some of the [message] [src == user ? "your" : "[src]'s"] [affecting.name].")) return TRUE + +/// Sets both mob's and eye organ's eye color values +/// If color_right is not passed, its assumed to be the same as color_left +/mob/living/carbon/human/proc/set_eye_color(color_left, color_right) + if (!color_right) + color_right = color_left + eye_color_left = color_left + eye_color_right = color_right + // Doesn't assign eye color if they already have one from their type + var/obj/item/organ/eyes/eyes = get_organ_by_type(/obj/item/organ/eyes) + if (istype(eyes) && !initial(eyes.eye_color_left) && !initial(eyes.eye_color_right)) + eyes.eye_color_left = color_left + eyes.eye_color_right = color_right + eyes.refresh(src, FALSE) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 74def434a31..562ad5d8190 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -3279,6 +3279,7 @@ if (eyes && !IS_ROBOTIC_ORGAN(eyes)) eyes.eye_color_left = color eyes.eye_color_right = color + affected_human.update_body() /datum/reagent/luminescent_fluid/red name = "Red Luminiscent Fluid" diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm index 41fa9babe7a..4df1873c001 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm @@ -22,6 +22,8 @@ var/extend_sound = 'sound/vehicles/mecha/mechmove03.ogg' /// Sound played when retracting var/retract_sound = 'sound/vehicles/mecha/mechmove03.ogg' + /// Do we have a separate icon_state for the hand overlay? + var/hand_state = TRUE /obj/item/organ/cyberimp/arm/Initialize(mapload) . = ..() @@ -89,6 +91,25 @@ // give the owner an idea about why his implant is glitching Retract() +/obj/item/organ/cyberimp/arm/get_overlay_state(image_layer, obj/item/bodypart/limb) + return "[aug_overlay][zone == BODY_ZONE_L_ARM ? "_left" : "_right"]" + +/obj/item/organ/cyberimp/arm/get_overlay(image_layer, obj/item/bodypart/limb) + if (!hand_state) + return ..() + + var/mutable_appearance/arm_overlay = mutable_appearance( + icon = aug_icon, + icon_state = get_overlay_state(), + layer = image_layer, + ) + var/mutable_appearance/hand_overlay = mutable_appearance( + icon = aug_icon, + icon_state = "[get_overlay_state()]_hand", + layer = -BODYPARTS_HIGH_LAYER, + ) + return list(arm_overlay, hand_overlay) + /** * Called when the mob uses the "drop item" hotkey * @@ -223,6 +244,7 @@ name = "integrated toolset implant" desc = "A stripped-down version of the engineering cyborg toolset, designed to be installed on subject's arm. Contain advanced versions of every tool." icon_state = "toolkit_engineering" + aug_overlay = "toolkit_engi" actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit) items_to_create = list( /obj/item/screwdriver/cyborg, @@ -238,6 +260,7 @@ name = "integrated paperwork implant" desc = "A highly sought out implant among heads of personnel, and other high up command staff in Nanotrasen. This implant allows the user to always have the tools necessary for paperwork handy" icon_state = "toolkit_engineering" + aug_overlay = "toolkit_engi" actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit) items_to_create = list( /obj/item/pen/fountain, @@ -277,11 +300,14 @@ /obj/item/organ/cyberimp/arm/medibeam name = "integrated medical beamgun" desc = "A cybernetic implant that allows the user to project a healing beam from their hand." + icon_state = "toolkit_surgical" + aug_overlay = "toolkit_med" items_to_create = list(/obj/item/gun/medbeam) /obj/item/organ/cyberimp/arm/flash name = "integrated high-intensity photon projector" //Why not desc = "An integrated projector mounted onto a user's arm that is able to be used as a powerful flash." + aug_overlay = "toolkit" items_to_create = list(/obj/item/assembly/flash/armimplant) /obj/item/organ/cyberimp/arm/flash/Initialize(mapload) @@ -306,11 +332,13 @@ /obj/item/organ/cyberimp/arm/baton name = "arm electrification implant" desc = "An illegal combat implant that allows the user to administer disabling shocks from their arm." + aug_overlay = "toolkit" items_to_create = list(/obj/item/borg/stun) /obj/item/organ/cyberimp/arm/combat name = "combat cybernetics implant" desc = "A powerful cybernetic implant that contains combat modules built into the user's arm." + aug_overlay = "toolkit" items_to_create = list( /obj/item/melee/energy/blade/hardlight, /obj/item/gun/medbeam, @@ -331,6 +359,7 @@ name = "surgical toolset implant" desc = "A set of surgical tools hidden behind a concealed panel on the user's arm." icon_state = "toolkit_surgical" + aug_overlay = "toolkit_med" actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit) items_to_create = list( /obj/item/retractor/augment, @@ -345,6 +374,7 @@ /obj/item/organ/cyberimp/arm/surgery/emagged name = "hacked surgical toolset implant" desc = "A set of surgical tools hidden behind a concealed panel on the user's arm. This one seems to have been tampered with." + aug_overlay = "toolkit_med" items_to_create = list( /obj/item/retractor/augment, /obj/item/hemostat/augment, @@ -374,6 +404,8 @@ ) actions_types = list() + aug_overlay = "strongarm" + hand_state = FALSE ///The amount of damage the implant adds to our unarmed attacks. var/punch_damage = 5 diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm index a26bc7b7de0..c5dedb71fab 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -7,6 +7,7 @@ name = "nutriment pump implant" desc = "This implant will synthesize and pump into your bloodstream a small amount of nutriment when you are starving." icon_state = "nutriment_implant" + aug_overlay = "nutripump" var/hunger_threshold = NUTRITION_LEVEL_STARVING var/synthesizing = 0 var/poison_amount = 5 @@ -37,6 +38,7 @@ name = "nutriment pump implant PLUS" desc = "This implant will synthesize and pump into your bloodstream a small amount of nutriment when you are hungry." icon_state = "adv_nutriment_implant" + aug_overlay = "nutripump_adv" hunger_threshold = NUTRITION_LEVEL_HUNGRY poison_amount = 10 @@ -44,6 +46,8 @@ name = "reviver implant" desc = "This implant will attempt to revive and heal you if you lose consciousness. For the faint of heart!" icon_state = "reviver_implant" + aug_overlay = "reviver" + emissive_overlay = TRUE slot = ORGAN_SLOT_HEART_AID var/revive_cost = 0 var/reviving = FALSE @@ -159,7 +163,8 @@ slot = ORGAN_SLOT_THRUSTERS icon_state = "imp_jetpack" base_icon_state = "imp_jetpack" - implant_color = null + aug_overlay = "imp_jetpack" + emissive_overlay = TRUE actions_types = list(/datum/action/item_action/organ_action/toggle) w_class = WEIGHT_CLASS_NORMAL var/on = FALSE @@ -208,6 +213,7 @@ if(!silent) to_chat(owner, span_notice("You turn your thrusters set on.")) update_appearance() + owner.update_body_parts() /obj/item/organ/cyberimp/chest/thrusters/proc/deactivate(silent = FALSE) if(!on) @@ -218,6 +224,7 @@ to_chat(owner, span_notice("You turn your thrusters set off.")) on = FALSE update_appearance() + owner.update_body_parts() /obj/item/organ/cyberimp/chest/thrusters/update_icon_state() icon_state = "[base_icon_state][on ? "-on" : null]" @@ -258,6 +265,14 @@ deactivate(silent = TRUE) return FALSE +/obj/item/organ/cyberimp/chest/thrusters/get_overlay_state(image_layer, obj/item/bodypart/limb) + return "[aug_overlay][on ? "_on" : ""]" + +/obj/item/organ/cyberimp/chest/thrusters/get_overlay(image_layer, obj/item/bodypart/limb) + . = ..() + for (var/image/overlay as anything in .) + overlay.layer = -BODYPARTS_HIGH_LAYER // makes absolutely zero sense why it would layer ontop of jumpsuits but it looks cool + /obj/item/organ/cyberimp/chest/spine name = "\improper Herculean gravitronic spinal implant" desc = "This gravitronic spinal interface is able to improve the athletics of a user, allowing them greater physical ability. \ @@ -304,23 +319,24 @@ remove_organ_trait(TRAIT_STURDY_FRAME) /obj/item/organ/cyberimp/chest/spine/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - . = ..() + if(!istype(tool, /obj/item/assembly/signaler/anomaly/grav)) + return NONE + if(core_applied) user.balloon_alert(user, "core already installed!") return ITEM_INTERACT_BLOCKING - if(istype(tool, /obj/item/assembly/signaler/anomaly/grav)) - user.balloon_alert(user, "core installed.") - name = /obj/item/organ/cyberimp/chest/spine/atlas::name - desc = /obj/item/organ/cyberimp/chest/spine/atlas::desc - athletics_boost_multiplier = /obj/item/organ/cyberimp/chest/spine/atlas::athletics_boost_multiplier - added_throw_range = /obj/item/organ/cyberimp/chest/spine/atlas::added_throw_range - added_throw_speed = /obj/item/organ/cyberimp/chest/spine/atlas::added_throw_speed - strength_bonus = /obj/item/organ/cyberimp/chest/spine/atlas::strength_bonus - core_applied = TRUE - update_appearance() - qdel(tool) - return ITEM_INTERACT_SUCCESS + user.balloon_alert(user, "core installed") + name = /obj/item/organ/cyberimp/chest/spine/atlas::name + desc = /obj/item/organ/cyberimp/chest/spine/atlas::desc + athletics_boost_multiplier = /obj/item/organ/cyberimp/chest/spine/atlas::athletics_boost_multiplier + added_throw_range = /obj/item/organ/cyberimp/chest/spine/atlas::added_throw_range + added_throw_speed = /obj/item/organ/cyberimp/chest/spine/atlas::added_throw_speed + strength_bonus = /obj/item/organ/cyberimp/chest/spine/atlas::strength_bonus + core_applied = TRUE + update_appearance() + qdel(tool) + return ITEM_INTERACT_SUCCESS /obj/item/organ/cyberimp/chest/spine/atlas name = "\improper Atlas gravitonic spinal implant" diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm index 525400b5419..9763b22e4b0 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm @@ -15,48 +15,61 @@ var/HUD_traits = list() /// Whether the HUD implant is on or off var/toggled_on = TRUE + /// Eyecolor from the HUD + var/hud_color = "#3CB8A5" -/obj/item/organ/cyberimp/eyes/hud/proc/toggle_hud(mob/living/carbon/eye_owner) +/obj/item/organ/cyberimp/eyes/hud/proc/toggle_hud(mob/living/carbon/human/eye_owner) if(toggled_on) toggled_on = FALSE eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT) balloon_alert(eye_owner, "hud disabled") + if(hud_color) + eye_owner.remove_eye_color(EYE_COLOR_HUD_PRIORITY) return toggled_on = TRUE eye_owner.add_traits(HUD_traits, ORGAN_TRAIT) balloon_alert(eye_owner, "hud enabled") + if(hud_color) + eye_owner.add_eye_color_right(hud_color, EYE_COLOR_HUD_PRIORITY) -/obj/item/organ/cyberimp/eyes/hud/on_mob_insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) +/obj/item/organ/cyberimp/eyes/hud/on_mob_insert(mob/living/carbon/human/eye_owner, special = FALSE, movement_flags) . = ..() - eye_owner.add_traits(HUD_traits, ORGAN_TRAIT) toggled_on = TRUE + if(hud_color) + eye_owner.add_eye_color_right(hud_color, EYE_COLOR_HUD_PRIORITY, !special) -/obj/item/organ/cyberimp/eyes/hud/on_mob_remove(mob/living/carbon/eye_owner, special, movement_flags) +/obj/item/organ/cyberimp/eyes/hud/on_mob_remove(mob/living/carbon/human/eye_owner, special, movement_flags) . = ..() eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT) toggled_on = FALSE + if(hud_color) + eye_owner.remove_eye_color(EYE_COLOR_HUD_PRIORITY, !special) /obj/item/organ/cyberimp/eyes/hud/medical name = "medical HUD implant" desc = "These cybernetic eye implants will display a medical HUD over everything you see." icon_state = "eye_implant_medical" HUD_traits = list(TRAIT_MEDICAL_HUD) + hud_color = "#1D8FEC" /obj/item/organ/cyberimp/eyes/hud/security name = "security HUD implant" desc = "These cybernetic eye implants will display a security HUD over everything you see." icon_state = "eye_implant_security" HUD_traits = list(TRAIT_SECURITY_HUD) + hud_color = "#9A151E" /obj/item/organ/cyberimp/eyes/hud/diagnostic name = "diagnostic HUD implant" desc = "These cybernetic eye implants will display a diagnostic HUD over everything you see." icon_state = "eye_implant_diagnostic" HUD_traits = list(TRAIT_DIAGNOSTIC_HUD, TRAIT_BOT_PATH_HUD) + hud_color = "#CC6E33" /obj/item/organ/cyberimp/eyes/hud/security/syndicate name = "contraband security HUD implant" desc = "A Cybersun Industries brand Security HUD Implant. These illicit cybernetic eye implants will display a security HUD over everything you see." icon_state = "eye_implant_syndicate" organ_flags = ORGAN_ROBOTIC | ORGAN_HIDDEN + hud_color = null diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm index fc358e2c5f6..d604344ba98 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -5,7 +5,72 @@ organ_flags = ORGAN_ROBOTIC failing_desc = "seems to be broken." - var/implant_color = COLOR_WHITE + /// icon of the bodypart overlay we're going to be applying to our owner + var/aug_icon = 'icons/mob/human/species/misc/bodypart_overlay_augmentations.dmi' + /// icon_state of the bodypart overlay we're going to be applying to our owner + var/aug_overlay = null + /// Does the implant have an emissive overlay too? + var/emissive_overlay = FALSE + /// Bodypart overlay we're going to apply to whoever we're implanted into + var/datum/bodypart_overlay/augment/bodypart_aug = null + +/obj/item/organ/cyberimp/Initialize(mapload) + . = ..() + if (aug_overlay) + bodypart_aug = new(src) + +/obj/item/organ/cyberimp/Destroy() + QDEL_NULL(bodypart_aug) + return ..() + +/obj/item/organ/cyberimp/proc/get_overlay_state() + return aug_overlay + +/obj/item/organ/cyberimp/proc/get_overlay(image_layer, obj/item/bodypart/limb) + . = list() + . += image(icon = aug_icon, icon_state = get_overlay_state(), layer = image_layer) + if (emissive_overlay) + . += emissive_appearance(aug_icon, "[get_overlay_state()]_e", limb.owner || limb, image_layer) + +/obj/item/organ/cyberimp/on_bodypart_insert(obj/item/bodypart/limb) + . = ..() + if (bodypart_aug) + limb.add_bodypart_overlay(bodypart_aug) + +/obj/item/organ/cyberimp/on_bodypart_remove(obj/item/bodypart/limb) + . = ..() + if (bodypart_aug) + limb.remove_bodypart_overlay(bodypart_aug) + +/datum/bodypart_overlay/augment + layers = EXTERNAL_ADJACENT + /// Implant that owns this overlay + var/obj/item/organ/cyberimp/implant + +/datum/bodypart_overlay/augment/New(obj/item/organ/cyberimp/implant) + . = ..() + src.implant = implant + +/datum/bodypart_overlay/augment/Destroy(force) + implant = null + return ..() + +/datum/bodypart_overlay/augment/generate_icon_cache() + . = ..() + . += implant.get_overlay_state() + +/datum/bodypart_overlay/augment/get_overlay(layer, obj/item/bodypart/limb) + layer = bitflag_to_layer(layer) + var/list/imageset = implant.get_overlay(layer, limb) + if(blocks_emissive == EMISSIVE_BLOCK_NONE || !limb) + return imageset + + var/list/all_images = list() + for(var/image/overlay as anything in imageset) + all_images += overlay + all_images += emissive_blocker(overlay.icon, overlay.icon_state, limb, layer = overlay.layer, alpha = overlay.alpha) + + return all_images /obj/item/organ/cyberimp/feel_for_damage(self_aware) // No feeling in implants (yet?) @@ -284,6 +349,7 @@ icon_state = "implant_mask" slot = ORGAN_SLOT_BREATHING_TUBE w_class = WEIGHT_CLASS_TINY + aug_overlay = "breathing_tube" /obj/item/organ/cyberimp/mouth/breathing_tube/emp_act(severity) . = ..() diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index f8d66ede5a5..5ff3aa14a9b 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -37,8 +37,8 @@ /// Are these eyes immune to pepperspray? var/pepperspray_protect = FALSE - var/eye_color_left = "" //set to a hex code to override a mob's left eye color - var/eye_color_right = "" //set to a hex code to override a mob's right eye color + var/eye_color_left = null // set to a hex code to override a mob's left eye color + var/eye_color_right = null // set to a hex code to override a mob's right eye color var/eye_icon_state = "eyes" /// Do these eyes have blinking animations var/blink_animation = TRUE @@ -74,6 +74,13 @@ . = ..() receiver.cure_blind(NO_EYES) apply_damaged_eye_effects() + // Ensures that non-player mobs get their eye colors assigned, as players get them from prefs + if (ishuman(receiver)) + var/mob/living/carbon/human/as_human = receiver + if (!eye_color_left) + eye_color_left = as_human.eye_color_left + if (!eye_color_right) + eye_color_right = as_human.eye_color_right refresh(receiver, call_update = !special) RegisterSignal(receiver, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act)) RegisterSignal(receiver, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(on_face_wash)) @@ -90,9 +97,9 @@ return var/mob/living/carbon/human/affected_human = eye_owner - if(length(eye_color_left)) + if(eye_color_left) affected_human.add_eye_color_left(eye_color_left, EYE_COLOR_ORGAN_PRIORITY, update_body = FALSE) - if(length(eye_color_right)) + if(eye_color_right) affected_human.add_eye_color_right(eye_color_right, EYE_COLOR_ORGAN_PRIORITY, update_body = FALSE) refresh_atom_color_overrides() @@ -152,9 +159,9 @@ var/left_color = COLOR_WHITE var/right_color = COLOR_WHITE - if (length(eye_color_left)) + if (eye_color_left) left_color = eye_color_left - if (length(eye_color_right)) + if (eye_color_right) right_color = eye_color_right if (checked_color[ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_FILTER) @@ -295,7 +302,7 @@ left_scar.blend_mode = BLEND_INSET_OVERLAY . += left_scar - if (iris_overlay && length(eye_color_left) && length(eye_color_right)) + if (iris_overlay && eye_color_left && eye_color_right) var/mutable_appearance/left_iris = mutable_appearance(icon, "[iris_overlay]_l") var/mutable_appearance/right_iris = mutable_appearance(icon, "[iris_overlay]_r") var/list/color_left = rgb2num(eye_color_left, COLORSPACE_HSL) @@ -427,15 +434,7 @@ eyelid_right.render_target = "*[REF(parent)]_eyelid_right" parent.vis_contents += eyelid_left parent.vis_contents += eyelid_right - var/sync_blinking = synchronized_blinking && (parent.get_organ_loss(ORGAN_SLOT_BRAIN) < ASYNC_BLINKING_BRAIN_DAMAGE) - // Randomize order for unsynched animations - if (sync_blinking || prob(50)) - var/list/anim_times = animate_eyelid(eyelid_left, parent, sync_blinking) - animate_eyelid(eyelid_right, parent, sync_blinking, anim_times) - else - var/list/anim_times = animate_eyelid(eyelid_right, parent, sync_blinking) - animate_eyelid(eyelid_left, parent, sync_blinking, anim_times) - + animate_eyelids(parent) var/mutable_appearance/left_eyelid_overlay = mutable_appearance(layer = -BODY_LAYER, offset_spokesman = parent) var/mutable_appearance/right_eyelid_overlay = mutable_appearance(layer = -BODY_LAYER, offset_spokesman = parent) left_eyelid_overlay.render_source = "*[REF(parent)]_eyelid_left" @@ -447,14 +446,28 @@ . = list() var/prevent_loops = HAS_TRAIT(parent, TRAIT_PREVENT_BLINK_LOOPS) animate(eyelid, alpha = 0, time = 0, loop = (prevent_loops ? 0 : -1)) - for (var/i in 1 to (prevent_loops ? 1 : BLINK_LOOPS)) - var/wait_time = rand(BASE_BLINKING_DELAY - RAND_BLINKING_DELAY, BASE_BLINKING_DELAY + RAND_BLINKING_DELAY) + + var/wait_time = rand(BASE_BLINKING_DELAY - RAND_BLINKING_DELAY, BASE_BLINKING_DELAY + RAND_BLINKING_DELAY) + if (anim_times) + if (sync_blinking) + wait_time = anim_times[1] + anim_times.Cut(1, 2) + else + wait_time = rand(max(BASE_BLINKING_DELAY - RAND_BLINKING_DELAY, anim_times[1] - RAND_BLINKING_DELAY), anim_times[1]) + + animate(time = wait_time) + . += wait_time + + var/cycles = (prevent_loops ? 1 : BLINK_LOOPS) + for (var/i in 1 to cycles) if (anim_times) if (sync_blinking) wait_time = anim_times[1] anim_times.Cut(1, 2) else wait_time = rand(max(BASE_BLINKING_DELAY - RAND_BLINKING_DELAY, anim_times[1] - RAND_BLINKING_DELAY), anim_times[1]) + else + wait_time = rand(BASE_BLINKING_DELAY - RAND_BLINKING_DELAY, BASE_BLINKING_DELAY + RAND_BLINKING_DELAY) . += wait_time if (anim_times && !sync_blinking) // Make sure that we're somewhat in sync with the other eye @@ -462,8 +475,38 @@ anim_times.Cut(1, 2) animate(alpha = 255, time = 0) animate(time = BLINK_DURATION) - animate(alpha = 0, time = 0) - animate(time = wait_time) + if (i != cycles) + animate(alpha = 0, time = 0) + animate(time = wait_time) + +/obj/item/organ/eyes/proc/blink(duration = BLINK_DURATION, restart_animation = TRUE) + var/left_delayed = rand(50) + // Storing blink delay so mistimed blinks of lizards don't get cut short + var/blink_delay = synchronized_blinking ? rand(0, RAND_BLINKING_DELAY) : 0 + animate(eyelid_left, alpha = 0, time = 0) + if (!synchronized_blinking && left_delayed) + animate(time = blink_delay) + animate(alpha = 255, time = 0) + animate(time = duration) + animate(alpha = 0, time = 0) + animate(eyelid_right, alpha = 0, time = 0) + if (!synchronized_blinking && !left_delayed) + animate(time = blink_delay) + animate(alpha = 255, time = 0) + animate(time = duration) + animate(alpha = 0, time = 0) + if (restart_animation) + addtimer(CALLBACK(src, PROC_REF(animate_eyelids), owner), blink_delay + duration) + +/obj/item/organ/eyes/proc/animate_eyelids(mob/living/carbon/human/parent) + var/sync_blinking = synchronized_blinking && (parent.get_organ_loss(ORGAN_SLOT_BRAIN) < ASYNC_BLINKING_BRAIN_DAMAGE) + // Randomize order for unsynched animations + if (sync_blinking || prob(50)) + var/list/anim_times = animate_eyelid(eyelid_left, parent, sync_blinking) + animate_eyelid(eyelid_right, parent, sync_blinking, anim_times) + else + var/list/anim_times = animate_eyelid(eyelid_right, parent, sync_blinking) + animate_eyelid(eyelid_left, parent, sync_blinking, anim_times) /obj/effect/abstract/eyelid_effect name = "eyelid" @@ -640,8 +683,8 @@ /obj/item/organ/eyes/robotic/flashlight name = "flashlight eyes" desc = "It's two flashlights rigged together with some wire. Why would you put these in someone's head?" - eye_color_left ="fee5a3" - eye_color_right ="fee5a3" + eye_color_left ="#fee5a3" + eye_color_right ="#fee5a3" icon = 'icons/obj/lighting.dmi' icon_state = "flashlight_eyes" flash_protect = FLASH_PROTECTION_WELDER diff --git a/code/modules/unit_tests/changeling.dm b/code/modules/unit_tests/changeling.dm index 7f86510fd62..cc39b471d09 100644 --- a/code/modules/unit_tests/changeling.dm +++ b/code/modules/unit_tests/changeling.dm @@ -82,8 +82,7 @@ ling.dna.features["spines"] = "Long + Membrane" ling.dna.features["lizard_markings"] = "Light Belly" ling.dna.features["legs"] = DIGITIGRADE_LEGS - ling.eye_color_left = COLOR_WHITE - ling.eye_color_right = COLOR_WHITE + ling.set_eye_color(COLOR_WHITE) ling.dna.update_ui_block(DNA_EYE_COLOR_LEFT_BLOCK) ling.dna.update_ui_block(DNA_EYE_COLOR_RIGHT_BLOCK) ling.set_species(/datum/species/lizard) diff --git a/icons/mob/human/species/misc/bodypart_overlay_augmentations.dmi b/icons/mob/human/species/misc/bodypart_overlay_augmentations.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a8e122dfe529d9c7fa3ed474e275ec5b72c79b82 GIT binary patch literal 4898 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