From d4edb72a3b41b774d9bc644d14b84e9554efc985 Mon Sep 17 00:00:00 2001 From: Alexios Date: Fri, 24 Jan 2025 18:32:17 -0600 Subject: [PATCH] Yeah so I impulsively ported the entirety of this system without knowing what it did --- code/__DEFINES/color_defines.dm | 17 +++ code/__DEFINES/flags.dm | 16 +++ code/__HELPERS/colour_helpers.dm | 109 ++++++++++++++++++ code/__HELPERS/filters.dm | 6 + code/__HELPERS/trait_helpers.dm | 11 ++ code/_globalvars/traits.dm | 3 + code/datums/components/paintable.dm | 2 +- code/game/atoms.dm | 59 +++++++--- code/game/objects/items/crayons.dm | 1 + code/game/objects/items/dyeing.dm | 58 +++++----- .../clothing/under/jobs/security_jumpsuits.dm | 2 + .../living/carbon/human/human_update_icons.dm | 13 ++- code/modules/power/lights.dm | 2 +- 13 files changed, 250 insertions(+), 49 deletions(-) diff --git a/code/__DEFINES/color_defines.dm b/code/__DEFINES/color_defines.dm index 558dd7d88c9..d89af64fd1d 100644 --- a/code/__DEFINES/color_defines.dm +++ b/code/__DEFINES/color_defines.dm @@ -157,3 +157,20 @@ /// Color for dead external organs/zombies #define COLORTONE_DEAD_EXT_ORGAN "#0A3200" + + +/// If this is a plain atom color +#define ATOM_COLOR_TYPE_NORMAL "normal" +/// If this is a color filter +#define ATOM_COLOR_TYPE_FILTER "filter" +// Indexes for color arrays +#define ATOM_COLOR_VALUE_INDEX 1 +#define ATOM_COLOR_TYPE_INDEX 2 +#define ATOM_PRIORITY_COLOR_FILTER "atom_priority_color" +#define ATOM_PRIORITY_COLOR_FILTER_PRIORITY -1 +/// Multiply pixel's saturation by color's saturation. Paints accents while keeping dim areas dim. +#define SATURATION_MULTIPLY "multiply" +/// Always affects the original pixel's saturation and lightness. +#define SATURATION_OVERRIDE "always" + + diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 08c5087b4b0..c2f222c3b85 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -202,3 +202,19 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define SCOPE_CLICK_MIDDLE (1<<2) /// Should the user hold the item in active hand to use it? #define SCOPE_NEED_ACTIVE_HAND (1<<3) + +// for next define +#define KEEP_TOGETHER_ORIGINAL "keep_together_original" + +//setter for KEEP_TOGETHER to allow for multiple sources to set and unset it +#define ADD_KEEP_TOGETHER(x, source)\ + if ((x.appearance_flags & KEEP_TOGETHER) && !HAS_TRAIT(x, TRAIT_KEEP_TOGETHER)) ADD_TRAIT(x, TRAIT_KEEP_TOGETHER, KEEP_TOGETHER_ORIGINAL); \ + ADD_TRAIT(x, TRAIT_KEEP_TOGETHER, source);\ + x.appearance_flags |= KEEP_TOGETHER + +#define REMOVE_KEEP_TOGETHER(x, source)\ + REMOVE_TRAIT(x, TRAIT_KEEP_TOGETHER, source);\ + if(HAS_TRAIT_FROM_ONLY(x, TRAIT_KEEP_TOGETHER, KEEP_TOGETHER_ORIGINAL))\ + REMOVE_TRAIT(x, TRAIT_KEEP_TOGETHER, KEEP_TOGETHER_ORIGINAL);\ + else if(!HAS_TRAIT(x, TRAIT_KEEP_TOGETHER))\ + x.appearance_flags &= ~KEEP_TOGETHER diff --git a/code/__HELPERS/colour_helpers.dm b/code/__HELPERS/colour_helpers.dm index 2c8668fd400..947443af3ae 100644 --- a/code/__HELPERS/colour_helpers.dm +++ b/code/__HELPERS/colour_helpers.dm @@ -45,3 +45,112 @@ RGB[2] = clamp(RGB[2]+value,0,255) RGB[3] = clamp(RGB[3]+value,0,255) return rgb(RGB[1],RGB[2],RGB[3]) + +//PACS related code + +/proc/color_transition_filter(new_color, saturation_behavior = SATURATION_MULTIPLY) + if (islist(new_color)) + new_color = rgb(new_color[1], new_color[2], new_color[3]) + new_color = rgb2num(new_color, COLORSPACE_HSL) + var/hue = new_color[1] / 360 + var/saturation = new_color[2] / 100 + var/added_saturation = 0 + var/deducted_light = 0 + if (saturation_behavior == SATURATION_OVERRIDE) + added_saturation = saturation * 0.75 + deducted_light = saturation * 0.5 + saturation = min(saturation, 1 - added_saturation) + + var/list/new_matrix = list( + 0, 0, 0, 0, // Ignore original hue + 0, saturation, 0, 0, // Multiply the saturation by ours + 0, 0, 1 - deducted_light, 0, // If we're highly saturated then remove a bit of lightness to keep some color in + 0, 0, 0, 1, // Preserve alpha + hue, added_saturation, 0, 0, // And apply our preferred hue and some saturation if we're oversaturated + ) + return color_matrix_filter(new_matrix, FILTER_COLOR_HSL) + +/// Applies a color filter to a hex/RGB list color +/proc/apply_matrix_to_color(color, list/matrix, colorspace = COLORSPACE_HSL) + if (islist(color)) + color = rgb(color[1], color[2], color[3], color[4]) + color = rgb2num(color, colorspace) + // Pad alpha if we're lacking it + if (length(color) < 4) + color += 255 + + // Do we have a constants row? + var/has_constants = FALSE + // Do we have an alpha row/parameters? + var/has_alpha = FALSE + + switch (length(matrix)) + if (9) + has_constants = FALSE + has_alpha = FALSE + if (12) + has_constants = TRUE + has_alpha = FALSE + if (16) + has_constants = FALSE + has_alpha = TRUE + if (20) + has_constants = TRUE + has_alpha = TRUE + else + CRASH("Matrix of invalid length [length(matrix)] was passed into apply_matrix_to_color!") + + var/list/new_color = list(0, 0, 0, 0) + var/row_length = 3 + if (has_alpha) + row_length = 4 + else + new_color[4] = 255 + + for (var/row_index in 1 to length(matrix) / row_length) + for (var/row_elem in 1 to row_length) + var/elem = matrix[(row_index - 1) * row_length + row_elem] + if (!has_constants || row_index != (length(matrix) / row_length)) + new_color[row_index] += color[row_elem] * elem + continue + + // Constant values at the end of the list (if we have such) + if (colorspace != COLORSPACE_HSV && colorspace != COLORSPACE_HCY && colorspace != COLORSPACE_HSL) + new_color[row_elem] += elem * 255 + continue + + // HSV/HSL/HCY have non-255 maximums for their values + var/multiplier = 255 + switch (row_elem) + // Hue goes from 0 to 360 + if (1) + multiplier = 360 + // Value, luminance, chroma, etc go from 0 to 100 + if (2 to 3) + multiplier = 100 + // Alpha still goes from 0 to 255 + if (4) + multiplier = 255 + new_color[row_elem] += elem * multiplier + + var/rgbcolor = rgb(new_color[1], new_color[2], new_color[3], new_color[4], space = colorspace) + return rgbcolor + +/// Recursively applies a filter to a passed in static appearance, returns the modified appearance +/proc/filter_appearance_recursive(mutable_appearance/filter, filter_to_apply) + var/mutable_appearance/modify = new(filter) + var/list/existing_filters = modify.filters.Copy() + modify.filters = list(filter_to_apply) + existing_filters + + // Ideally this should be recursive to check for KEEP_APART elements that need this applied to it + // and RESET_COLOR flags but this is much simpler, and hopefully we don't have that point of layering here + if(modify.appearance_flags & KEEP_TOGETHER) + return modify + + for(var/overlay_index in 1 to length(modify.overlays)) + modify.overlays[overlay_index] = filter_appearance_recursive(modify.overlays[overlay_index], filter_to_apply) + + for(var/underlay_index in 1 to length(modify.underlays)) + modify.underlays[underlay_index] = filter_appearance_recursive(modify.underlays[underlay_index], filter_to_apply) + + return modify diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm index c086658a614..8fda5437ecb 100644 --- a/code/__HELPERS/filters.dm +++ b/code/__HELPERS/filters.dm @@ -331,3 +331,9 @@ GLOBAL_LIST_INIT(master_filter_info, list( .["offset"] = offset if(!isnull(alpha)) .["alpha"] = alpha + +// PACS related +/proc/convert_list_to_filter(list/list_filter) + var/list/arguments = list_filter.Copy() + arguments -= "priority" + return filter(arglist(arguments)) diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm index 82492782822..97eca9929ff 100644 --- a/code/__HELPERS/trait_helpers.dm +++ b/code/__HELPERS/trait_helpers.dm @@ -131,6 +131,9 @@ /// Gives a unique trait source for any given datum #define UNIQUE_TRAIT_SOURCE(target) "unique_source_[target.UID()]" + + + /* Remember to update _globalvars/traits.dm if you're adding/removing/renaming traits. */ @@ -384,6 +387,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai //traits that should be properly converted to genetic mutations one day #define TRAIT_LASEREYES "laser_eyes" + +/// Trait aquired from being painted a certain color +#define ATOM_COLOR_TRAIT "atom_color" + + //status effec traits /// Forces the user to stay unconscious. #define TRAIT_KNOCKEDOUT "knockedout" @@ -449,3 +457,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Proc wrapper of remove_trait. You should only use this for callback. Otherwise, use the macro. /proc/callback_remove_trait(datum/target, trait, source) REMOVE_TRAIT(target, trait, source) + +///Used for managing KEEP_TOGETHER in [appearance_flags] +#define TRAIT_KEEP_TOGETHER "keep-together" diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index c9a69c212ba..4b11d184cfa 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -154,6 +154,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( /atom/movable = list( "TRAIT_NO_EDGE_TRANSITIONS" = TRAIT_NO_EDGE_TRANSITIONS + ), + /atom = list( + "TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER ) )) diff --git a/code/datums/components/paintable.dm b/code/datums/components/paintable.dm index f0a7187149a..dd722e0757e 100644 --- a/code/datums/components/paintable.dm +++ b/code/datums/components/paintable.dm @@ -26,6 +26,6 @@ var/colour = spraycan.colour current_paint = colour var/atom/A = parent - A.add_atom_colour(colour, FIXED_COLOUR_PRIORITY) + A.add_atom_colour(color_transition_filter(colour, SATURATION_MULTIPLY), ADMIN_COLOUR_PRIORITY) playsound(spraycan, 'sound/effects/spray.ogg', 5, TRUE, 5) to_chat(user, "You spray [spraycan] on [A], painting it.") diff --git a/code/game/atoms.dm b/code/game/atoms.dm index b11e4567c29..5c60cf0144e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -59,6 +59,9 @@ var/list/atom_colours //used to store the different colors on an atom //its inherent color, the colored paint applied on it, special color effect etc... + // currently used Color filter, cached because we apply it to all of our overlays. According to the original PR author, "Byond is Horrific." + var/cached_color_filter + /// Radiation insulation types var/rad_insulation = RAD_NO_INSULATION @@ -1245,7 +1248,12 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) return if(colour_priority > length(atom_colours)) return - atom_colours[colour_priority] = coloration + var/color_type = ATOM_COLOR_TYPE_NORMAL + if (islist(coloration)) + var/list/color_matrix = coloration + if(color_matrix["type"]=="color") + color_type = ATOM_COLOR_TYPE_FILTER + atom_colours[colour_priority] = list(coloration, color_type) update_atom_colour() /* @@ -1257,8 +1265,13 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. if(colour_priority > length(atom_colours)) return - if(coloration && atom_colours[colour_priority] != coloration) - return //if we don't have the expected color (for a specific priority) to remove, do nothing + if(coloration && atom_colours[colour_priority]) + if (atom_colours[colour_priority][ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_NORMAL) + if(atom_colours[colour_priority][ATOM_COLOR_VALUE_INDEX]!=coloration) + return // if we don't have the expected color to remove, don't do anything. + else + if(!islist(coloration) || !compare_list(coloration,atom_colours[colour_priority][ATOM_COLOR_VALUE_INDEX]["color"])) + return atom_colours[colour_priority] = null update_atom_colour() @@ -1267,19 +1280,35 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons) colour available */ /atom/proc/update_atom_colour() + var/old_filter = cached_color_filter + cached_color_filter = null + remove_filter(ATOM_PRIORITY_COLOR_FILTER) + REMOVE_KEEP_TOGETHER(src, ATOM_COLOR_TRAIT) + if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - color = null - for(var/C in atom_colours) - if(islist(C)) - var/list/L = C - if(length(L)) - color = L - return - else if(C) - color = C - return + if (old_filter) + update_appearance() + return + for(var/list/checked_color in atom_colours) + if(checked_color[ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_FILTER) + add_filter(ATOM_PRIORITY_COLOR_FILTER, ATOM_PRIORITY_COLOR_FILTER_PRIORITY, checked_color[ATOM_COLOR_VALUE_INDEX]) + cached_color_filter = checked_color[ATOM_COLOR_VALUE_INDEX] + break + if(length(checked_color[ATOM_COLOR_VALUE_INDEX])) + color = checked_color[ATOM_COLOR_VALUE_INDEX] + break + + ADD_KEEP_TOGETHER(src, ATOM_COLOR_TRAIT) + if(cached_color_filter != old_filter) + update_appearance() + +/// Same as update_atom_color, but simplifies overlay coloring +/atom/proc/color_atom_overlay(mutable_appearance/overlay) + overlay.color = color + if (!cached_color_filter) + return overlay + return filter_appearance_recursive(overlay, cached_color_filter) + /** Call this when you want to present a renaming prompt to the user. diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 36ab5e3bd7a..ba627a3159d 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -38,6 +38,7 @@ /obj/item/toy/crayon/suicide_act(mob/user) user.visible_message("[user] is jamming the [name] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!") + user.add_atom_colour(color_transition_filter(colour, SATURATION_OVERRIDE), ADMIN_COLOUR_PRIORITY) return BRUTELOSS|OXYLOSS /obj/item/toy/crayon/Initialize(mapload) diff --git a/code/game/objects/items/dyeing.dm b/code/game/objects/items/dyeing.dm index 722826491d0..8ea23aba1a0 100644 --- a/code/game/objects/items/dyeing.dm +++ b/code/game/objects/items/dyeing.dm @@ -14,36 +14,42 @@ if(!dyeable || !dye_color) return var/dye_key_selector = dye_key_override ? dye_key_override : dyeing_key - if(!dye_key_selector) - return FALSE - if(!GLOB.dye_registry[dye_key_selector]) - stack_trace("Item just tried to be dyed with an invalid registry key: [dye_key_selector]") - return FALSE - var/obj/item/target_type = GLOB.dye_registry[dye_key_selector][dye_color] - if(!target_type) - return FALSE - var/obj/item/target_obj = new target_type - // update icons - icon = initial(target_obj.icon) - icon_state = initial(target_obj.icon_state) - item_state = initial(target_obj.item_state) - sprite_sheets = target_obj.sprite_sheets - item_color = target_obj.item_color - desc = target_obj.desc - base_icon_state = target_obj.base_icon_state + var/has_key = TRUE // If not, we're gonna assign it using the PACS system. + if(dye_key_selector) + if(!GLOB.dye_registry[dye_key_selector]) + stack_trace("Item just tried to be dyed with an invalid registry key: [dye_key_selector]") + return FALSE + var/obj/item/target_type = GLOB.dye_registry[dye_key_selector][dye_color] + var/obj/item/target_obj = new target_type + if(!target_type) + return FALSE + // update icons + if(has_key) + icon = initial(target_obj.icon) + icon_state = initial(target_obj.icon_state) + item_state = initial(target_obj.item_state) + sprite_sheets = target_obj.sprite_sheets + item_color = target_obj.item_color + desc = target_obj.desc + base_icon_state = target_obj.base_icon_state - // update inhand sprites - lefthand_file = initial(target_obj.lefthand_file) - righthand_file = initial(target_obj.righthand_file) - inhand_x_dimension = initial(target_obj.inhand_x_dimension) - inhand_y_dimension = initial(target_obj.inhand_y_dimension) + // update inhand sprites + lefthand_file = initial(target_obj.lefthand_file) + righthand_file = initial(target_obj.righthand_file) + inhand_x_dimension = initial(target_obj.inhand_x_dimension) + inhand_y_dimension = initial(target_obj.inhand_y_dimension) + + // update the name/description + name = initial(target_obj.name) + qdel(target_obj) + else + has_key = FALSE + add_atom_colour(color_transition_filter(dye_color, SATURATION_MULTIPLY), ADMIN_COLOUR_PRIORITY) - // update the name/description - name = initial(target_obj.name) desc += "\nThe colors look a little dodgy." - qdel(target_obj) + update_appearance(ALL) - return target_type + return TRUE /// Beanies use the color var for their appearance, we don't normally copy this over but we have to for beanies /obj/item/clothing/head/beanie/dye_item(dye_color, dye_key_override) diff --git a/code/modules/clothing/under/jobs/security_jumpsuits.dm b/code/modules/clothing/under/jobs/security_jumpsuits.dm index d92bf4123b6..0ad1b7418a6 100644 --- a/code/modules/clothing/under/jobs/security_jumpsuits.dm +++ b/code/modules/clothing/under/jobs/security_jumpsuits.dm @@ -2,6 +2,8 @@ icon = 'icons/obj/clothing/under/security.dmi' armor = list(MELEE = 5, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, RAD = 0, FIRE = 20, ACID = 20) strip_delay = 50 + dyeable = TRUE + dyeing_key = FALSE sprite_sheets = list( "Human" = 'icons/mob/clothing/under/security.dmi', "Vox" = 'icons/mob/clothing/species/vox/under/security.dmi', 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 bbac701116e..84a1789fee3 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -614,7 +614,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) else standing.overlays += image("icon" = 'icons/mob/accessories.dmi', "icon_state" = "[tie_color]") standing.alpha = w_uniform.alpha - standing.color = w_uniform.color + standing = w_uniform.color_atom_overlay(standing) overlays_standing[UNIFORM_LAYER] = standing else if(!dna.species.nojumpsuit) @@ -816,7 +816,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) bloodsies.color = shoes.blood_color standing.overlays += bloodsies standing.alpha = shoes.alpha - standing.color = shoes.color + standing = shoes.color_atom_overlay(standing) overlays_standing[SHOES_LAYER] = standing else if(feet_blood_DNA) // Checks for feet to make sure we don't have mysterious floating blood. @@ -887,7 +887,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) bloodsies.color = head.blood_color standing.overlays += bloodsies standing.alpha = head.alpha - standing.color = head.color + standing = head.color_atom_overlay(standing) overlays_standing[HEAD_LAYER] = standing apply_overlay(HEAD_LAYER) @@ -979,7 +979,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) standing.overlays += special_overlays standing.alpha = wear_suit.alpha - standing.color = wear_suit.color + standing = wear_suit.color_atom_overlay(standing) overlays_standing[SUIT_LAYER] = standing apply_overlay(SUIT_LAYER) @@ -1056,7 +1056,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) standing.overlays += bloodsies standing.alpha = wear_mask.alpha - standing.color = wear_mask.color + standing = wear_mask.color_atom_overlay(standing) overlays_standing[FACEMASK_LAYER] = standing apply_overlay(FACEMASK_LAYER) @@ -1107,7 +1107,8 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //create the image standing.alpha = back.alpha - standing.color = back.color + //standing.color = back.color + standing = back.color_atom_overlay(standing) overlays_standing[BACK_LAYER] = standing apply_overlay(BACK_LAYER) diff --git a/code/modules/power/lights.dm b/code/modules/power/lights.dm index bf048012cb6..467c19192ec 100644 --- a/code/modules/power/lights.dm +++ b/code/modules/power/lights.dm @@ -613,7 +613,7 @@ hsl[3] = max(hsl[3], 0.4) var/list/rgb = hsl2rgb(arglist(hsl)) var/new_color = "#[num2hex(rgb[1], 2)][num2hex(rgb[2], 2)][num2hex(rgb[3], 2)]" - color = new_color + color = add_atom_colour(color_transition_filter(new_color, SATURATION_MULTIPLY), ADMIN_COLOUR_PRIORITY) to_chat(user, "You change [src]'s light bulb color.") brightness_color = new_color update(TRUE, TRUE, FALSE)