Skip to content

Commit

Permalink
Yeah so I impulsively ported the entirety of this system without know…
Browse files Browse the repository at this point in the history
…ing what it did
  • Loading branch information
Alecksohs committed Jan 25, 2025
1 parent 0a08b79 commit d4edb72
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 49 deletions.
17 changes: 17 additions & 0 deletions code/__DEFINES/color_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"


16 changes: 16 additions & 0 deletions code/__DEFINES/flags.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
109 changes: 109 additions & 0 deletions code/__HELPERS/colour_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions code/__HELPERS/filters.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
11 changes: 11 additions & 0 deletions code/__HELPERS/trait_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
3 changes: 3 additions & 0 deletions code/_globalvars/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
))

Expand Down
2 changes: 1 addition & 1 deletion code/datums/components/paintable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<span class='notice'>You spray [spraycan] on [A], painting it.</span>")
59 changes: 44 additions & 15 deletions code/game/atoms.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

/*
Expand All @@ -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()

Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions code/game/objects/items/crayons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

/obj/item/toy/crayon/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[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!</span>")
user.add_atom_colour(color_transition_filter(colour, SATURATION_OVERRIDE), ADMIN_COLOUR_PRIORITY)
return BRUTELOSS|OXYLOSS

/obj/item/toy/crayon/Initialize(mapload)
Expand Down
58 changes: 32 additions & 26 deletions code/game/objects/items/dyeing.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions code/modules/clothing/under/jobs/security_jumpsuits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit d4edb72

Please sign in to comment.