diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index acbbb10dccf..1e3392948ba 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -734,3 +734,5 @@ do { \
#define INGREDIENT_CHECK_EXACT 1
#define INGREDIENT_CHECK_FAILURE 0
#define INGREDIENT_CHECK_SURPLUS -1
+
+#define ALPHA_VISIBLE 255 // the max alpha
diff --git a/code/game/dna/mutations/mutation_powers.dm b/code/game/dna/mutations/mutation_powers.dm
index e24f4b8819a..9cdd0861b68 100644
--- a/code/game/dna/mutations/mutation_powers.dm
+++ b/code/game/dna/mutations/mutation_powers.dm
@@ -209,19 +209,29 @@
..()
block = GLOB.shadowblock
+/datum/mutation/stealth/darkcloak/deactivate(mob/living/M)
+ ..()
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ H.set_alpha_tracking(ALPHA_VISIBLE, src)
+ if(!ishuman(M))
+ return
+ var/mob/living/carbon/human/H = M
+ H.set_alpha_tracking(ALPHA_VISIBLE, src)
/datum/mutation/stealth/darkcloak/on_life(mob/M)
var/turf/simulated/T = get_turf(M)
- if(!istype(T))
+ if(!istype(T) || !ishuman(M))
return
+ var/mob/living/carbon/human/H = M
var/light_available = T.get_lumcount() * 10
if(light_available <= 2)
- if(M.invisibility != INVISIBILITY_LEVEL_TWO)
- M.alpha = round(M.alpha * 0.8)
+ if(H.invisibility != INVISIBILITY_LEVEL_TWO)
+ H.set_alpha_tracking(H.get_alpha() * 0.8, src)
else
- M.reset_visibility()
- M.alpha = round(255 * 0.8)
- if(M.alpha == 0)
- M.make_invisible()
+ H.reset_visibility()
+ H.set_alpha_tracking(ALPHA_VISIBLE * 0.8, src)
+ if(H.get_alpha(src) == 0)
+ H.make_invisible()
//WAS: /datum/bioEffect/chameleon
/datum/mutation/stealth/chameleon
@@ -234,15 +244,24 @@
..()
block = GLOB.chameleonblock
-/datum/mutation/stealth/chameleon/on_life(mob/living/M) //look if a ghost gets this, its an admins problem
- if((world.time - M.last_movement) >= 30 && !M.stat && (M.mobility_flags & MOBILITY_STAND) && !M.restrained())
- if(M.invisibility != INVISIBILITY_LEVEL_TWO)
- M.alpha -= 25
+/datum/mutation/stealth/chameleon/deactivate(mob/living/M)
+ ..()
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ H.set_alpha_tracking(ALPHA_VISIBLE, src)
+
+/datum/mutation/stealth/chameleon/on_life(mob/living/M)
+ if(!ishuman(M))
+ return
+ var/mob/living/carbon/human/H = M
+ if((world.time - H.last_movement) >= 30 && !H.stat && (H.mobility_flags & MOBILITY_STAND) && !H.restrained())
+ if(H.invisibility != INVISIBILITY_LEVEL_TWO)
+ H.set_alpha_tracking(H.get_alpha() - 25, src)
else
- M.reset_visibility()
- M.alpha = round(255 * 0.80)
- if(M.alpha == 0)
- M.make_invisible()
+ H.reset_visibility()
+ H.set_alpha_tracking(ALPHA_VISIBLE * 0.8, src)
+ if(H.get_alpha(src) == 0)
+ H.make_invisible()
/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index a34e31f6738..e6921569f7b 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -292,13 +292,20 @@
RegisterSignal(user, COMSIG_CARBON_REGENERATE_ICONS, PROC_REF(reapply_hide))
mob_overlay = mutable_appearance(icon, icon_state, user.layer, user.plane, 255, appearance_flags = RESET_COLOR | RESET_TRANSFORM | RESET_ALPHA | KEEP_APART)
user.add_overlay(mob_overlay)
- user.alpha = 0
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.set_alpha_tracking(0, src)
+ else
+ user.alpha = 0
/// User has either dropped the plant, or plant is being destroyed, restore user to normal.
/obj/item/kirbyplants/proc/unhide_user(mob/living/carbon/user)
UnregisterSignal(user, COMSIG_CARBON_REGENERATE_ICONS)
user.cut_overlay(mob_overlay)
user.alpha = initial(user.alpha)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.set_alpha_tracking(ALPHA_VISIBLE, src)
QDEL_NULL(mob_overlay)
/// Icon operation has occured, time to make sure we're showing a plant again if we need to be.
diff --git a/code/modules/antagonists/changeling/powers/environmental_adaption.dm b/code/modules/antagonists/changeling/powers/environmental_adaption.dm
index 6935bbdb3be..7d7e4623abe 100644
--- a/code/modules/antagonists/changeling/powers/environmental_adaption.dm
+++ b/code/modules/antagonists/changeling/powers/environmental_adaption.dm
@@ -16,6 +16,12 @@
var/recharge_slowdown = 0.15
var/chem_recharge_slowdown = 0
+/datum/action/changeling/environmental_adaptation/Destroy(force, ...)
+ if(ishuman(cling.owner.current))
+ var/mob/living/carbon/human/H = cling.owner.current
+ H.set_alpha_tracking(ALPHA_VISIBLE, src)
+ return ..()
+
/datum/action/changeling/environmental_adaptation/sting_action(mob/living/carbon/human/cling) //SHOULD always be human, because req_human = TRUE
..()
is_active = !is_active
@@ -26,14 +32,16 @@
/datum/action/changeling/environmental_adaptation/proc/enable_ability(mob/living/carbon/human/cling) //Enable the adaptation
- animate(cling, alpha = 65, time = 3 SECONDS)
+ cling.set_alpha_tracking(65, src, update_alpha = FALSE)
+ animate(cling, alpha = cling.get_alpha(), time = 3 SECONDS)
cling.visible_message("[cling]'s skin suddenly starts becoming translucent!", \
"We adapt our pigmentation to suit the environment around us.")
var/datum/antagonist/changeling/changeling_data = cling.mind?.has_antag_datum(/datum/antagonist/changeling)
changeling_data?.chem_recharge_slowdown -= recharge_slowdown //Slows down chem regeneration
/datum/action/changeling/environmental_adaptation/proc/disable_ability(mob/living/carbon/human/cling) //Restore the adaptation
- animate(cling, alpha = 255, time = 3 SECONDS)
+ cling.set_alpha_tracking(ALPHA_VISIBLE, src, update_alpha = FALSE)
+ animate(cling, alpha = cling.get_alpha(), time = 3 SECONDS)
cling.visible_message(
"[cling] appears from thin air!",
"We stop concentration on our pigmentation, allowing it to return to normal.",
diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm
index cc62c38b72d..3fe4dcc7660 100644
--- a/code/modules/antagonists/vampire/vamp_datum.dm
+++ b/code/modules/antagonists/vampire/vamp_datum.dm
@@ -36,11 +36,17 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
blurb_a = 1
/datum/antagonist/vampire/Destroy(force, ...)
- owner.current.create_log(CONVERSION_LOG, "De-vampired")
draining = null
QDEL_NULL(subclass)
return ..()
+/datum/antagonist/vampire/detach_from_owner()
+ owner.current.create_log(CONVERSION_LOG, "De-vampired")
+ if(ishuman(owner.current))
+ var/mob/living/carbon/human/human_owner = owner.current
+ human_owner.set_alpha_tracking(ALPHA_VISIBLE, src)
+ return ..()
+
/datum/antagonist/vampire/add_owner_to_gamemode()
SSticker.mode.vampires += owner
@@ -258,24 +264,25 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
if(!ishuman(owner.current))
owner.current.alpha = 255
return
- var/turf/simulated/T = get_turf(owner.current)
+ var/mob/living/carbon/human/human_owner = owner.current
+ var/turf/simulated/T = get_turf(human_owner)
var/light_available = T.get_lumcount() * 10
if(!istype(T))
return
- if(!iscloaking || owner.current.on_fire)
- owner.current.alpha = 255
- REMOVE_TRAIT(owner.current, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT)
+ if(!iscloaking || human_owner.on_fire)
+ human_owner.set_alpha_tracking(ALPHA_VISIBLE, src)
+ REMOVE_TRAIT(human_owner, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT)
return
if(light_available <= 2)
- owner.current.alpha = 38 // round(255 * 0.15)
- ADD_TRAIT(owner.current, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT)
+ human_owner.set_alpha_tracking(ALPHA_VISIBLE * 0.15, src)
+ ADD_TRAIT(human_owner, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT)
return
- REMOVE_TRAIT(owner.current, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT)
- owner.current.alpha = 204 // 255 * 0.80
+ REMOVE_TRAIT(human_owner, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT)
+ human_owner.set_alpha_tracking(ALPHA_VISIBLE * 0.80, src)
/**
* Handles unique drain ID checks and increases vampire's total and usable blood by blood_amount. Checks for ability upgrades.
diff --git a/code/modules/mining/lavaland/loot/hierophant_loot.dm b/code/modules/mining/lavaland/loot/hierophant_loot.dm
index 66a94acf1ad..eb5b621a630 100644
--- a/code/modules/mining/lavaland/loot/hierophant_loot.dm
+++ b/code/modules/mining/lavaland/loot/hierophant_loot.dm
@@ -242,6 +242,9 @@
var/turf/turf_to_teleport_to = get_step(target, get_dir(source, M)) //get position relative to caster
if(!turf_to_teleport_to || is_blocked_turf(turf_to_teleport_to, TRUE))
return
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ H.set_alpha_tracking(0, src, update_alpha = FALSE)
animate(M, alpha = 0, time = 2, easing = EASE_OUT) //fade out
sleep(1)
if(!M)
@@ -254,7 +257,12 @@
sleep(1)
if(!M)
return
- animate(M, alpha = 255, time = 2, easing = EASE_IN) //fade IN
+ var/our_alpha = 255
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ H.set_alpha_tracking(ALPHA_VISIBLE, src, update_alpha = FALSE)
+ our_alpha = H.get_alpha()
+ animate(M, alpha = our_alpha, time = 2, easing = EASE_IN) //fade IN
sleep(1)
if(!M)
return
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 5cf1b5218fc..501eda399e7 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -103,3 +103,6 @@
var/list/bodyparts = list()
/// map organ names to organs
var/list/bodyparts_by_name = list()
+
+ /// Lazylist of sources to track what our alpha should be, alpha is set to the minimum. Use the `set_alpha_tracking` and `get_alpha` helpers.
+ var/list/alpha_sources
diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm
index 2a68cfd0467..c73e1df7734 100644
--- a/code/modules/mob/living/carbon/human/human_mob.dm
+++ b/code/modules/mob/living/carbon/human/human_mob.dm
@@ -2034,3 +2034,30 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
dna.ResetUIFrom(src)
flavor_text = ""
+/**
+ * Helper for tracking alpha, use this to set an alpha source
+ *
+ * alpha_num - num between 0 and 255 that represents the alpha from `source`
+ * source - a datum that is the cause of the alpha source. This will be turned into a key via UID.
+ * update_alpha - boolean if alpha should be updated with this proc. Set this to false if you plan to animate the alpha after this call.
+ */
+/mob/living/carbon/human/proc/set_alpha_tracking(alpha_num, datum/source, update_alpha = TRUE)
+ alpha_num = round(alpha_num)
+ if(alpha_num >= ALPHA_VISIBLE)
+ LAZYREMOVE(alpha_sources, source.UID())
+ else
+ LAZYSET(alpha_sources, source.UID(), max(alpha_num, 0))
+ if(update_alpha)
+ alpha = get_alpha()
+
+/**
+ * Gets the target alpha of the human
+ *
+ * optional_source - use this to get the alpha of an exact source. This is unsafe, only use if you 100% know it will be in the list. For the best safety, only call this as get_alpha(src)
+ */
+/mob/living/carbon/human/proc/get_alpha(datum/optional_source)
+ if(optional_source)
+ return alpha_sources[optional_source.UID()]
+ . = ALPHA_VISIBLE
+ for(var/source in alpha_sources)
+ . = min(., alpha_sources[source])
diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm
index 95a6c790308..47f744999fe 100644
--- a/code/modules/mob/living/carbon/human/human_update_icons.dm
+++ b/code/modules/mob/living/carbon/human/human_update_icons.dm
@@ -564,6 +564,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
update_fire()
update_icons()
update_emissive_block()
+ alpha = get_alpha()
if(player_logged) //make sure the SSD overlay stays
overlays += image('icons/effects/effects.dmi', icon_state = "zzz_glow")
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 8a8e806f86b..3dcac7d1c69 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1360,6 +1360,10 @@ GLOBAL_LIST_INIT(holy_areas, typecacheof(list(
alpha = initial(alpha)
add_to_all_human_data_huds()
+/mob/living/carbon/human/reset_visibility()
+ ..()
+ alpha = get_alpha()
+
/mob/proc/make_invisible()
invisibility = INVISIBILITY_LEVEL_TWO
alpha = 128
diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm
index 835de26f048..3bfdf62ccb6 100644
--- a/code/modules/mod/modules/modules_antag.dm
+++ b/code/modules/mod/modules/modules_antag.dm
@@ -288,7 +288,8 @@
RegisterSignal(mod.wearer, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
RegisterSignal(mod.wearer, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act))
RegisterSignals(mod.wearer, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_HITBY, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW), PROC_REF(unstealth))
- animate(mod.wearer, alpha = stealth_alpha, time = 1.5 SECONDS)
+ mod.wearer.set_alpha_tracking(stealth_alpha, src, update_alpha = FALSE)
+ animate(mod.wearer, alpha = mod.wearer.get_alpha(), time = 1.5 SECONDS)
drain_power(use_power_cost)
/obj/item/mod/module/stealth/on_deactivation(display_message = TRUE, deleting = FALSE)
@@ -298,7 +299,8 @@
if(bumpoff)
UnregisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP)
UnregisterSignal(mod.wearer, list(COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ITEM_ATTACK, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW))
- animate(mod.wearer, alpha = 255, time = 1.5 SECONDS)
+ mod.wearer.set_alpha_tracking(ALPHA_VISIBLE, src, update_alpha = FALSE)
+ animate(mod.wearer, alpha = mod.wearer.get_alpha(), time = 1.5 SECONDS)
/obj/item/mod/module/stealth/proc/unstealth(datum/source)
SIGNAL_HANDLER