From 818f2d64e0ad40a329749cbac444db1693a8afba Mon Sep 17 00:00:00 2001 From: BlackMajor Date: Thu, 2 Mar 2023 20:41:12 +1300 Subject: [PATCH] yeah --- code/__defines/lum_ch.dm | 4 + code/_helpers/icons.dm | 4 +- code/_helpers/icons_ch.dm | 645 ++++++++++++++++++ code/game/machinery/painter_vr.dm | 3 + code/modules/mob/mob_helpers.dm | 4 +- modular_chomp/code/_DEFINES/color_priority.dm | 11 + modular_chomp/code/_HELPERS/icons/flatten.dm | 268 ++++++++ .../code/_HELPERS/type2type/color.dm | 172 +++++ .../datums/browser/color_matrix_picker.dm | 84 +++ modular_chomp/code/game/atoms/atoms.dm | 55 ++ .../code/game/machinery/colormate.dm | 304 +++++++++ modular_chomp/code/matrices/color_matrix.dm | 240 +++++++ vorestation.dme | 10 +- 13 files changed, 1799 insertions(+), 5 deletions(-) create mode 100644 code/__defines/lum_ch.dm create mode 100644 code/_helpers/icons_ch.dm create mode 100644 modular_chomp/code/_DEFINES/color_priority.dm create mode 100644 modular_chomp/code/_HELPERS/icons/flatten.dm create mode 100644 modular_chomp/code/_HELPERS/type2type/color.dm create mode 100644 modular_chomp/code/datums/browser/color_matrix_picker.dm create mode 100644 modular_chomp/code/game/atoms/atoms.dm create mode 100644 modular_chomp/code/game/machinery/colormate.dm create mode 100644 modular_chomp/code/matrices/color_matrix.dm diff --git a/code/__defines/lum_ch.dm b/code/__defines/lum_ch.dm new file mode 100644 index 0000000000..4d152073d3 --- /dev/null +++ b/code/__defines/lum_ch.dm @@ -0,0 +1,4 @@ +//Luma coefficients suggested for HDTVs. If you change these, make sure they add up to 1. +#define LUMA_R 0.213 +#define LUMA_G 0.715 +#define LUMA_B 0.072 \ No newline at end of file diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm index 82e135c544..b8c501be26 100644 --- a/code/_helpers/icons.dm +++ b/code/_helpers/icons.dm @@ -80,8 +80,8 @@ Blend(M, ICON_ADD) /proc/BlendRGB(rgb1, rgb2, amount) - var/list/RGB1 = rgb2num(rgb1) - var/list/RGB2 = rgb2num(rgb2) + var/list/RGB1 = ReadRGB(rgb1) //CHOMPEdit - Better rgb blend + var/list/RGB2 = ReadRGB(rgb2) // add missing alpha if needed if(RGB1.len < RGB2.len) RGB1 += 255 diff --git a/code/_helpers/icons_ch.dm b/code/_helpers/icons_ch.dm new file mode 100644 index 0000000000..5645cfc3bc --- /dev/null +++ b/code/_helpers/icons_ch.dm @@ -0,0 +1,645 @@ +/* +IconProcs README + +A BYOND library for manipulating icons and colors + +by Lummox JR + +version 1.0 + +The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation +routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. + +CHANGING ICONS + +Several new procs have been added to the /icon datum to simplify working with icons. To use them, +remember you first need to setup an /icon var like so: + +GLOBAL_DATUM_INIT(my_icon, /icon, new('iconfile.dmi')) + +icon/ChangeOpacity(amount = 1) + A very common operation in DM is to try to make an icon more or less transparent. Making an icon more + transparent is usually much easier than making it less so, however. This proc basically is a frontend + for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() + can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. + If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. +icon/GrayScale() + Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. +icon/ColorTone(tone) + Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an + RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. + See also the global ColorTone() proc. +icon/MinColors(icon) + The icon is blended with a second icon where the minimum of each RGB pixel is the result. + Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. +icon/MaxColors(icon) + The icon is blended with a second icon where the maximum of each RGB pixel is the result. + Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. +icon/Opaque(background = "#000000") + All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. +icon/BecomeAlphaMask() + You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. + The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. +icon/AddAlphaMask(mask) + The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, + the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. + Where the mask is translucent, the current icon becomes more transparent. +icon/UseAlphaMask(mask, mode) + Sometimes you may want to take the alpha values from one icon and use them on a different icon. + This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change + so it has the same colors as before but uses the mask for opacity. + +COLOR MANAGEMENT AND HSV + +RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. + + * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to + cyan to blue to magenta and back to red. + * The saturation of a color is how much color is in it. A color with low saturation will be more gray, + and with no saturation at all it is a shade of gray. + * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, + and no value at all is black. + +Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three +hex digits because it ranges from 0 to 0x5FF. + + * 0 to 0xFF - red to yellow + * 0x100 to 0x1FF - yellow to green + * 0x200 to 0x2FF - green to cyan + * 0x300 to 0x3FF - cyan to blue + * 0x400 to 0x4FF - blue to magenta + * 0x500 to 0x5FF - magenta to red + +Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), +value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". + +More than one HSV color can match the same RGB color. + +Here are some procs you can use for color management: + +ReadRGB(rgb) + Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used + that includes alpha, the list will have a fourth item for the alpha value. +hsv(hue, sat, val, apha) + Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" + format. Alpha is not included in the result if null. +ReadHSV(rgb) + Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that + includes alpha, the list will have a fourth item for the alpha value. +RGBtoHSV(rgb) + Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". +HSVtoRGB(hsv) + Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". +BlendRGB(rgb1, rgb2, amount) + Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; + if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an RGB or RGBA color. +BlendHSV(hsv1, hsv2, amount) + Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB + blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, + the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an HSV or HSVA color. +BlendRGBasHSV(rgb1, rgb2, amount) + Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. +HueToAngle(hue) + Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. +AngleToHue(hue) + Converts an angle to a hue in the valid range. +RotateHue(hsv, angle) + Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. + (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value + as the original, but a different hue. +GrayScale(rgb) + Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. +ColorTone(rgb, tone) + Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of + using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. +*/ + +/* +Get Flat Icon DEMO by DarkCampainger + +This is a test for the get flat icon proc, modified approprietly for icons and their states. +Probably not a good idea to run this unless you want to see how the proc works in detail. +mob + icon = 'old_or_unused.dmi' + icon_state = "green" + + Login() + // Testing image underlays + underlays += image(icon='old_or_unused.dmi',icon_state="red") + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) + + // Testing image overlays + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)) + + // Testing icon file overlays (defaults to mob's state) + add_overlay('_flat_demoIcons2.dmi') + + // Testing icon_state overlays (defaults to mob's icon) + add_overlay("white") + + // Testing dynamic icon overlays + var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") + I.Shift(NORTH,16,1) + add_overlay(I) + + // Testing dynamic image overlays + I=image(icon=I,pixel_x = -32, pixel_y = 32) + add_overlay(I) + + // Testing object types (and layers) + add_overlay(/obj/effect/overlayTest) + + loc = locate (10,10,1) + verb + Browse_Icon() + set name = "1. Browse Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Send the icon to src's local cache + src<

") + + Output_Icon() + set name = "2. Output Icon" + to_chat(src, "Icon is: [icon2base64html(get_flat_icon(src))]") + + Label_Icon() + set name = "3. Label Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Copy the file to the rsc manually + var/icon/I = fcopy_rsc(get_flat_icon(src)) + // Send the icon to src's local cache + src< 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 8) + break + + var/single = digits < 6 + if(digits != 3 && digits != 4 && digits != 6 && digits != 8) + return + if(digits == 4 || digits == 8) + usealpha = 1 + for(i=start, digits>0, ++i) + ch = text2ascii(rgb, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + r = (r << 4) | ch + if(single) + r |= r << 4 + ++which + else if(!(digits & 1)) + ++which + if(1) + g = (g << 4) | ch + if(single) + g |= g << 4 + ++which + else if(!(digits & 1)) + ++which + if(2) + b = (b << 4) | ch + if(single) + b |= b << 4 + ++which + else if(!(digits & 1)) + ++which + if(3) + alpha = (alpha << 4) | ch + if(single) + alpha |= alpha << 4 + + . = list(r, g, b) + if(usealpha) + . += alpha + +/proc/ReadHSV(hsv) + if(!hsv) + return + + // interpret the HSV or HSVA value + var/i=1,start=1 + if(text2ascii(hsv) == 35) + ++start // skip opening # + var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha + var/digits=0 + for(i=start, i<=length(hsv), ++i) + ch = text2ascii(hsv, i) + if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 9) + break + if(digits > 7) + usealpha = 1 + if(digits <= 4) + ++which + if(digits <= 2) + ++which + for(i=start, digits>0, ++i) + ch = text2ascii(hsv, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + hue = (hue << 4) | ch + if(digits == (usealpha ? 6 : 4)) + ++which + if(1) + sat = (sat << 4) | ch + if(digits == (usealpha ? 4 : 2)) + ++which + if(2) + val = (val << 4) | ch + if(digits == (usealpha ? 2 : 0)) + ++which + if(3) + alpha = (alpha << 4) | ch + + . = list(hue, sat, val) + if(usealpha) + . += alpha + +/proc/HSVtoRGB(hsv) + if(!hsv) + return "#000000" + var/list/HSV = ReadHSV(hsv) + if(!HSV) + return "#000000" + + var/hue = HSV[1] + var/sat = HSV[2] + var/val = HSV[3] + + // Compress hue into easier-to-manage range + hue -= hue >> 8 + if(hue >= 0x5fa) + hue -= 0x5fa + + var/hi,mid,lo,r,g,b + hi = val + lo = round((255 - sat) * val / 255, 1) + mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) + if(hue >= 765) + if(hue >= 1275) {r=hi; g=lo; b=mid} + else if(hue >= 1020) {r=mid; g=lo; b=hi } + else {r=lo; g=mid; b=hi } + else + if(hue >= 510) {r=lo; g=hi; b=mid} + else if(hue >= 255) {r=mid; g=hi; b=lo } + else {r=hi; g=mid; b=lo } + + return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) + +/proc/RGBtoHSV(rgb) + if(!rgb) + return "#0000000" + var/list/RGB = ReadRGB(rgb) + if(!RGB) + return "#0000000" + + var/r = RGB[1] + var/g = RGB[2] + var/b = RGB[3] + var/hi = max(r,g,b) + var/lo = min(r,g,b) + + var/val = hi + var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 + var/hue = 0 + + if(sat) + var/dir + var/mid + if(hi == r) + if(lo == b) {hue=0; dir=1; mid=g} + else {hue=1535; dir=-1; mid=b} + else if(hi == g) + if(lo == r) {hue=512; dir=1; mid=b} + else {hue=511; dir=-1; mid=r} + else if(hi == b) + if(lo == g) {hue=1024; dir=1; mid=r} + else {hue=1023; dir=-1; mid=g} + hue += dir * round((mid-lo) * 255 / (hi-lo), 1) + + return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) + +/proc/hsv(hue, sat, val, alpha) + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + if((hue & 0xFF) == 0xFF) + ++hue + if(hue >= 1536) + hue = 0 + if(sat < 0) + sat = 0 + if(sat > 255) + sat = 255 + if(val < 0) + val = 0 + if(val > 255) + val = 255 + . = "#" + . += TO_HEX_DIGIT(hue >> 8) + . += TO_HEX_DIGIT(hue >> 4) + . += TO_HEX_DIGIT(hue) + . += TO_HEX_DIGIT(sat >> 4) + . += TO_HEX_DIGIT(sat) + . += TO_HEX_DIGIT(val >> 4) + . += TO_HEX_DIGIT(val) + if(!isnull(alpha)) + if(alpha < 0) + alpha = 0 + if(alpha > 255) + alpha = 255 + . += TO_HEX_DIGIT(alpha >> 4) + . += TO_HEX_DIGIT(alpha) + +/* + Smooth blend between HSV colors + + amount=0 is the first color + amount=1 is the second color + amount=0.5 is directly between the two colors + + amount<0 or amount>1 are allowed + */ +/proc/BlendHSV(hsv1, hsv2, amount) + var/list/HSV1 = ReadHSV(hsv1) + var/list/HSV2 = ReadHSV(hsv2) + + // add missing alpha if needed + if(HSV1.len < HSV2.len) + HSV1 += 255 + else if(HSV2.len < HSV1.len) + HSV2 += 255 + var/usealpha = HSV1.len > 3 + + // normalize hsv values in case anything is screwy + if(HSV1[1] > 1536) + HSV1[1] %= 1536 + if(HSV2[1] > 1536) + HSV2[1] %= 1536 + if(HSV1[1] < 0) + HSV1[1] += 1536 + if(HSV2[1] < 0) + HSV2[1] += 1536 + if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} + if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} + + // no value for one color means don't change saturation + if(!HSV1[3]) + HSV1[2] = HSV2[2] + if(!HSV2[3]) + HSV2[2] = HSV1[2] + // no saturation for one color means don't change hues + if(!HSV1[2]) + HSV1[1] = HSV2[1] + if(!HSV2[2]) + HSV2[1] = HSV1[1] + + // Compress hues into easier-to-manage range + HSV1[1] -= HSV1[1] >> 8 + HSV2[1] -= HSV2[1] >> 8 + + var/hue_diff = HSV2[1] - HSV1[1] + if(hue_diff > 765) + hue_diff -= 1530 + else if(hue_diff <= -765) + hue_diff += 1530 + + var/hue = round(HSV1[1] + hue_diff * amount, 1) + var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) + var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) + var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null + + // normalize hue + if(hue < 0 || hue >= 1530) + hue %= 1530 + if(hue < 0) + hue += 1530 + // decompress hue + hue += round(hue / 255) + + return hsv(hue, sat, val, alpha) + +/proc/BlendRGBasHSV(rgb1, rgb2, amount) + return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) + +/proc/HueToAngle(hue) + // normalize hsv in case anything is screwy + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + // Compress hue into easier-to-manage range + hue -= hue >> 8 + return hue / (1530/360) + +/proc/AngleToHue(angle) + // normalize hsv in case anything is screwy + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + var/hue = angle * (1530/360) + // Decompress hue + hue += round(hue / 255) + return hue + + +// positive angle rotates forward through red->green->blue +/proc/RotateHue(hsv, angle) + var/list/HSV = ReadHSV(hsv) + + // normalize hsv in case anything is screwy + if(HSV[1] >= 1536) + HSV[1] %= 1536 + if(HSV[1] < 0) + HSV[1] += 1536 + + // Compress hue into easier-to-manage range + HSV[1] -= HSV[1] >> 8 + + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + HSV[1] = round(HSV[1] + angle * (1530/360), 1) + + // normalize hue + if(HSV[1] < 0 || HSV[1] >= 1530) + HSV[1] %= 1530 + if(HSV[1] < 0) + HSV[1] += 1530 + // decompress hue + HSV[1] += round(HSV[1] / 255) + + return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) + +// Convert an rgb color to grayscale +/proc/GrayScale(rgb) + var/list/RGB = ReadRGB(rgb) + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) + +// Change grayscale color to black->tone->white range +/proc/ColorTone(rgb, tone) + var/list/RGB = ReadRGB(rgb) + var/list/TONE = ReadRGB(tone) + + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 + + if(gray <= tone_gray) + return BlendRGB("#000000", tone, gray/(tone_gray || 1)) + else + return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) + + +//Used in the OLD chem colour mixing algorithm +/proc/GetColors(hex) + hex = uppertext(hex) + // No alpha set? Default to full alpha. + if(length(hex) == 7) + hex += "FF" + var/hi1 = text2ascii(hex, 2) // R + var/lo1 = text2ascii(hex, 3) // R + var/hi2 = text2ascii(hex, 4) // G + var/lo2 = text2ascii(hex, 5) // G + var/hi3 = text2ascii(hex, 6) // B + var/lo3 = text2ascii(hex, 7) // B + var/hi4 = text2ascii(hex, 8) // A + var/lo4 = text2ascii(hex, 9) // A + return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48), + ((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48), + ((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48), + ((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48)) + +//Interface for using DrawBox() to draw 1 pixel on a coordinate. +//Returns the same icon specifed in the argument, but with the pixel drawn +/proc/DrawPixel(icon/I,colour,drawX,drawY) + if(!I) + return 0 + + var/Iwidth = I.Width() + var/Iheight = I.Height() + + if(drawX > Iwidth || drawX <= 0) + return 0 + if(drawY > Iheight || drawY <= 0) + return 0 + + I.DrawBox(colour,drawX, drawY) + return I + + +//Interface for easy drawing of one pixel on an atom. +/atom/proc/DrawPixelOn(colour, drawX, drawY) + var/icon/I = new(icon) + var/icon/J = DrawPixel(I, colour, drawX, drawY) + if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. + icon = J + return J + return 0 + +//Hook, override to run code on- wait this is images +//Images have dir without being an atom, so they get their own definition. +//Lame. +/image/proc/setDir(newdir) + dir = newdir \ No newline at end of file diff --git a/code/game/machinery/painter_vr.dm b/code/game/machinery/painter_vr.dm index e10311915d..93698cffa4 100644 --- a/code/game/machinery/painter_vr.dm +++ b/code/game/machinery/painter_vr.dm @@ -1,3 +1,5 @@ +/* CHOMPStation edit - File overidden. See modular_chomp/code/game/machinery/colormate.dm + // I'm honestly pretty sure that short of stuffing five million things into this // there's absolutely no way it could ever have any performance impact // Given that all it does is set the color var @@ -115,3 +117,4 @@ . = TRUE update_icon() +/* \ No newline at end of file diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 7aee776ae9..9071db972d 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -85,10 +85,10 @@ /proc/is_admin(var/mob/user) return check_rights(R_ADMIN|R_EVENT, 0, user) != 0 - +/* CHOMPEdit - See modular_chomp/code/_HELPERS/type2type/color.dm /proc/hsl2rgb(h, s, l) return //TODO: Implement - +*/ /* Miss Chance */ diff --git a/modular_chomp/code/_DEFINES/color_priority.dm b/modular_chomp/code/_DEFINES/color_priority.dm new file mode 100644 index 0000000000..eb2dfdf6ca --- /dev/null +++ b/modular_chomp/code/_DEFINES/color_priority.dm @@ -0,0 +1,11 @@ +//different types of atom colorations +///only used by rare effects like greentext coloring mobs and when admins varedit color +#define ADMIN_COLOUR_PRIORITY 1 +///e.g. purple effect of the revenant on a mob, black effect when mob electrocuted +#define TEMPORARY_COLOUR_PRIORITY 2 +///color splashed onto an atom (e.g. paint on turf) +#define WASHABLE_COLOUR_PRIORITY 3 +///color inherent to the atom (e.g. blob color) +#define FIXED_COLOUR_PRIORITY 4 +///how many priority levels there are. +#define COLOUR_PRIORITY_AMOUNT 4 \ No newline at end of file diff --git a/modular_chomp/code/_HELPERS/icons/flatten.dm b/modular_chomp/code/_HELPERS/icons/flatten.dm new file mode 100644 index 0000000000..3424a5edd7 --- /dev/null +++ b/modular_chomp/code/_HELPERS/icons/flatten.dm @@ -0,0 +1,268 @@ +//? File contains functions to generate flat /icon's from things. +//? This is obviously expensive. Very, very expensive. +//? new get_flat_icon is faster, but, really, don't use these unless you need to. +//? Chances are unless you are: +//? - sending to html/browser (for non character preview purposes) +//? - taking photos +//? - doing complex icon operations that can't be done with filters/overlays +//? you probably don't need to use these. + +/** + * Generates an icon with all 4 directions of something. + * + * @params + * - A - appearancelike object. + * - no_anim - flatten out animations + */ +/proc/get_compound_icon(atom/A, no_anim) + var/mutable_appearance/N = new + N.appearance = A + N.dir = NORTH + var/icon/north = get_flat_icon(N, NORTH, no_anim = no_anim) + N.dir = SOUTH + var/icon/south = get_flat_icon(N, SOUTH, no_anim = no_anim) + N.dir = EAST + var/icon/east = get_flat_icon(N, EAST, no_anim = no_anim) + N.dir = WEST + var/icon/west = get_flat_icon(N, WEST, no_anim = no_anim) + qdel(N) + //Starts with a blank icon because of byond bugs. + var/icon/full = icon('icons/system/blank_32x32.dmi', "") + full.Insert(north, dir = NORTH) + full.Insert(south, dir = SOUTH) + full.Insert(east, dir = EAST) + full.Insert(west, dir = WEST) + qdel(north) + qdel(south) + qdel(east) + qdel(west) + return full + +/proc/get_flat_icon(appearance/appearancelike, dir, no_anim) + if(!dir && isloc(appearancelike)) + dir = appearancelike.dir + return _get_flat_icon(appearancelike, dir, no_anim, null, TRUE) + +/proc/_get_flat_icon(image/A, defdir, no_anim, deficon, start) + // start with blank image + var/static/icon/template = icon('icons/system/blank_32x32.dmi', "") + + #define BLANK icon(template) + + #define INDEX_X_LOW 1 + #define INDEX_X_HIGH 2 + #define INDEX_Y_LOW 3 + #define INDEX_Y_HIGH 4 + + #define flatX1 flat_size[INDEX_X_LOW] + #define flatX2 flat_size[INDEX_X_HIGH] + #define flatY1 flat_size[INDEX_Y_LOW] + #define flatY2 flat_size[INDEX_Y_HIGH] + #define addX1 add_size[INDEX_X_LOW] + #define addX2 add_size[INDEX_X_HIGH] + #define addY1 add_size[INDEX_Y_LOW] + #define addY2 add_size[INDEX_Y_HIGH] + + // invis? skip. + if(!A || A.alpha <= 0) + return BLANK + + // detect if state exists + var/icon/icon = A.icon || deficon + var/state = A.icon_state + var/none = !icon + if(!none) + var/list/states = icon_states(icon) + if(!(state in states)) + if(!("" in states)) + none = TRUE + else + state = "" + + // determine if there's directionals + // propagate forced direcitons down if and only if A has a direction + // todo: this results in a mismatch if someone is facing east but their overlays are facing south. + var/dir + if(start || !A.dir) + dir = defdir + else + dir = A.dir + var/ourdir = dir + if(!none && ourdir != SOUTH) + if(length(icon_states(icon(icon, state, NORTH)))) + else if(length(icon_states(icon(icon, state, EAST)))) + else if(length(icon_states(icon(icon, state, WEST)))) + else + ourdir = SOUTH + + // start generating + if(!A.overlays.len && !A.underlays.len) + // we don't even have ourselves! + if(none) + return BLANK + // no overlays/underlays, we're done, just mix in ourselves + var/icon/self_icon = icon(icon(icon, state, ourdir), "", SOUTH, no_anim? 1 : null) + if(A.alpha < 255) + self_icon.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + if(A.color) + if(islist(A.color)) + self_icon.MapColors(arglist(A.color)) + else + self_icon.Blend(A.color, ICON_MULTIPLY) + return self_icon + + // safety/performance check + if((A.overlays.len + A.underlays.len) > 80) + // we use fucking insertion check + // > 80 = death. + CRASH("get_flat_icon tried to process more than 80 layers") + + // otherwise, we have to blend in all overlays/underlays. + var/icon/flat = BLANK + var/list/appearance/gathered = list() + var/appearance/copying + var/appearance/comparing + var/i + var/appearance/self + var/current_layer + + if(!none) + // add the atom itself + self = image(icon = icon, icon_state = state, layer = A.layer, dir = ourdir) + self.color = A.color + self.alpha = A.alpha + self.blend_mode = A.blend_mode + gathered[self] = A.layer + + // gather + for(copying as anything in A.overlays) + // todo: better handling + if(copying.plane != FLOAT_PLANE && copying.plane != A.plane) + // we don't care probably HUD or something lol + continue + current_layer = copying.layer + // if it's float layer, shove it right above atom. + if(current_layer < 0) + if(current_layer < -1000) + CRASH("who the hell is using -1000 or below on float layers?") + current_layer = A.layer + (1000 + current_layer) / 1000 + // else, add 1 so it doesn't potentially collide on float + else + ++current_layer + + // inject with insertion sort + for(i in 1 to gathered.len) + comparing = gathered[i] + if(current_layer < gathered[comparing]) + gathered.Insert(i, copying) + // associate + gathered[copying] = current_layer + + for(copying as anything in A.underlays) + // todo: better handling + if(copying.plane != FLOAT_PLANE && copying.plane != A.plane) + // we don't care probably HUD or something lol + continue + current_layer = copying.layer + // if it's float layer, shove it right below atom. + if(current_layer < 0) + if(current_layer < -1000) + CRASH("who the hell is using -1000 or below on float layers?") + current_layer = A.layer - (1000 + current_layer) / 1000 + // else, subtract 1 so it doesn't potentially collide on float + else + --current_layer + + // inject with insertion sort + for(i in 1 to gathered.len) + comparing = gathered[i] + if(current_layer < gathered[comparing]) + gathered.Insert(i, copying) + // associate + gathered[copying] = current_layer + + // adding icon we're mixing in + var/icon/adding + // current dimensions + var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) + // adding dimensions + var/list/add_size[4] + // blend mode + var/blend_mode + + // blend in layers + for(copying as anything in gathered) + // if invis, skip + if(copying.alpha == 0) + continue + + // detect if it's literally ourselves + if(copying == self) + // blend in normally (no sense doing otherwise unless we're on map) + // we can't assume we're on map. + blend_mode = BLEND_OVERLAY + adding = icon(icon, state, ourdir) + else + // use full get_flat_icon + blend_mode = copying.blend_mode + adding = _get_flat_icon(copying, defdir, no_anim, icon) + + // if we got nothing, skip + if(!adding) + continue + + // detect adding size, taking into account copying overlay's pixel offsets + add_size[INDEX_X_LOW] = min(flatX1, copying.pixel_x + 1) + add_size[INDEX_X_HIGH] = max(flatX2, copying.pixel_x + adding.Width()) + add_size[INDEX_Y_LOW] = min(flatY1, copying.pixel_y + 1) + add_size[INDEX_Y_HIGH] = max(flatY2, copying.pixel_y + adding.Height()) + + // resize flat to fit if necessary + if(flat_size ~! add_size) + flat.Crop( + addX1 - flatX1 + 1, + addY1 - flatY1 + 1, + addX2 - flatX1 + 1, + addY2 - flatY1 + 1 + ) + flat_size = add_size.Copy() + + // blend the overlay/underlay in + flat.Blend(adding, blendMode2iconMode(blend_mode), copying.pixel_x + 2 - flatX1, copying.pixel_y + 2 - flatY1) + + // apply colors + if(A.color) + if(islist(A.color)) + flat.MapColors(arglist(A.color)) + else + flat.Blend(A.color, ICON_MULTIPLY) + + // apply alpha + if(A.alpha < 255) + flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + + // finalize + if(no_anim) + // clean up frames + var/icon/cleaned = icon() + cleaned.Insert(flat, "", SOUTH, 1, 0) + return cleaned + else + // just return flat as SOUTH + return icon(flat, "", SOUTH) + + #undef flatX1 + #undef flatX2 + #undef flatY1 + #undef flatY2 + #undef addX1 + #undef addX2 + #undef addY1 + #undef addY2 + + #undef INDEX_X_LOW + #undef INDEX_X_HIGH + #undef INDEX_Y_LOW + #undef INDEX_Y_HIGH + + #undef BLANK \ No newline at end of file diff --git a/modular_chomp/code/_HELPERS/type2type/color.dm b/modular_chomp/code/_HELPERS/type2type/color.dm new file mode 100644 index 0000000000..a09f01fa27 --- /dev/null +++ b/modular_chomp/code/_HELPERS/type2type/color.dm @@ -0,0 +1,172 @@ +/** + * Convert RBG to HSL + */ +/proc/rgb2hsl(red, green, blue) + red /= 255 + green /= 255 + blue /= 255 + + var/max = max(red, green, blue) + var/min = min(red, green, blue) + var/range = max - min + + var/hue = 0 + var/saturation = 0 + var/lightness = 0 + + lightness = (max + min) / 2 + if(range != 0) + if(lightness < 0.5) + saturation = range / (max + min) + else + saturation = range / (2 - max - min) + + var/dred = ((max - red) / (6 * max)) + 0.5 + var/dgreen = ((max - green) / (6 * max)) + 0.5 + var/dblue = ((max - blue) / (6 * max)) + 0.5 + + if(max == red) + hue = dblue - dgreen + else if(max == green) + hue = dred - dblue + (1 / 3) + else + hue = dgreen - dred + (2 / 3) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + + return list(hue, saturation, lightness) +/** + * Convert HSL to RGB + */ +/proc/hsl2rgb(hue, saturation, lightness) + var/red + var/green + var/blue + + if(saturation == 0) + red = lightness * 255 + green = red + blue = red + else + var/a;var/b; + if(lightness < 0.5) + b = lightness * (1 + saturation) + else + b = (lightness + saturation) - (saturation * lightness) + a = 2 * lightness - b + + red = round(255 * hue2rgb(a, b, hue + (1/3)), 1) + green = round(255 * hue2rgb(a, b, hue), 1) + blue = round(255 * hue2rgb(a, b, hue - (1/3)), 1) + + return list(red, green, blue) + +/** + * Convert hue to RGB + */ +/proc/hue2rgb(a, b, hue) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + if(6*hue < 1) + return (a + (b - a) * 6 * hue) + if(2*hue < 1) + return b + if(3*hue < 2) + return (a + (b - a) * ((2 / 3) - hue) * 6) + return a + +/** + * Convert Kelvin to RGB + * + * Adapted from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ + */ +/proc/heat2colour(temp) + return rgb( + heat2colour_r(temp), + heat2colour_g(temp), + heat2colour_b(temp), + ) + +/** + * Convert Kelvin for the Red channel + */ +/proc/heat2colour_r(temp) + temp /= 100 + if(temp <= 66) + . = 255 + else + . = max(0, min(255, 329.698727446 * (temp - 60) ** -0.1332047592)) + +/** + * Convert Kelvin for the Green channel + */ +/proc/heat2colour_g(temp) + temp /= 100 + if(temp <= 66) + . = max(0, min(255, 99.4708025861 * log(temp) - 161.1195681661)) + else + . = max(0, min(255, 288.1221685293 * ((temp - 60) ** -0.075148492))) + +/** + * Convert Kelvin for the Blue channel + */ +/proc/heat2colour_b(temp) + temp /= 100 + if(temp >= 66) + . = 255 + else + if(temp <= 16) + . = 0 + else + . = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307)) + +/** + * Assumes format #RRGGBB #rrggbb + */ +/proc/color_hex2num(A) + if(!A || length(A) != length_char(A)) + return 0 + var/R = hex2num(copytext(A, 2, 4)) + var/G = hex2num(copytext(A, 4, 6)) + var/B = hex2num(copytext(A, 6, 8)) + return R+G+B + +/** + *! Word of warning: + * Using a matrix like this as a color value will simplify it back to a string after being set. + */ +/proc/color_hex2color_matrix(string) + var/length = length(string) + if((length != 7 && length != 9) || length != length_char(string)) + return color_matrix_identity() + var/r = hex2num(copytext(string, 2, 4)) / 255 + var/g = hex2num(copytext(string, 4, 6)) / 255 + var/b = hex2num(copytext(string, 6, 8)) / 255 + var/a = 1 + if(length == 9) + a = hex2num(copytext(string, 8, 10)) / 255 + if(!isnum(r) || !isnum(g) || !isnum(b) || !isnum(a)) + return color_matrix_identity() + return list( + r,0,0,0,0, + g,0,0,0,0, + b,0,0,0,0, + a,0,0,0,0, + ) + +/** + * Will drop all values not on the diagonal. + */ +/proc/color_matrix2color_hex(list/the_matrix) + if(!istype(the_matrix) || the_matrix.len != 20) + return "#ffffffff" + return rgb( + the_matrix[1] * 255, // R + the_matrix[6] * 255, // G + the_matrix[11] * 255, // B + the_matrix[16] * 255, // A + ) \ No newline at end of file diff --git a/modular_chomp/code/datums/browser/color_matrix_picker.dm b/modular_chomp/code/datums/browser/color_matrix_picker.dm new file mode 100644 index 0000000000..fd409a3ba0 --- /dev/null +++ b/modular_chomp/code/datums/browser/color_matrix_picker.dm @@ -0,0 +1,84 @@ +/datum/browser/modal/color_matrix_picker + var/color_matrix + +/datum/browser/modal/color_matrix_picker/New(mob/user, message, title, button1 = "Ok", button2, button3, stealfocus = TRUE, timeout = 0, list/values) + if(!user) + return + if(!values) + values = list(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) + if(values.len < 12) + values.len = 12 + var/list/output = list() + output += "
" + output += "[message]" +#define MATRIX_FIELD(field, default) " " + output += "

" + output += MATRIX_FIELD("rr", values[1]) + output += MATRIX_FIELD("gr", values[4]) + output += MATRIX_FIELD("br", values[7]) + output += "

" + output += MATRIX_FIELD("rg", values[2]) + output += MATRIX_FIELD("gg", values[5]) + output += MATRIX_FIELD("bg", values[8]) + output += "

" + output += MATRIX_FIELD("rb", values[3]) + output += MATRIX_FIELD("gb", values[6]) + output += MATRIX_FIELD("bb", values[9]) + output += "

" + output += MATRIX_FIELD("cr", values[10]) + output += MATRIX_FIELD("cg", values[11]) + output += MATRIX_FIELD("cb", values[12]) + output += "

" +#undef MATRIX_FIELD + + output += {"
+ "} + + if (button2) + output += {""} + + if (button3) + output += {""} + output += {"
"} + + ..(user, ckey("[user]-[message]-[title]-[world.time]-[rand(1,10000)]"), title, 800, 400, src, stealfocus, timeout) + set_content(output.Join("")) + +/datum/browser/modal/color_matrix_picker/Topic(href, list/href_list) + if(href_list["close"] || !user) + opentime = 0 + return + if(href_list["button"]) + var/button = text2num(href_list["button"]) + if(ISINRANGE(button, 1, 3)) + selectedbutton = button + var/list/cm = rgb_construct_color_matrix( + text2num(href_list["rr"]), + text2num(href_list["rg"]), + text2num(href_list["rb"]), + text2num(href_list["gr"]), + text2num(href_list["gg"]), + text2num(href_list["gb"]), + text2num(href_list["br"]), + text2num(href_list["bg"]), + text2num(href_list["bb"]), + text2num(href_list["cr"]), + text2num(href_list["cg"]), + text2num(href_list["cb"]) + ) + if(cm) + color_matrix = cm + opentime = 0 + close() + +/proc/color_matrix_picker(mob/user, message, title, button1 = "Ok", button2, button3, stealfocus, timeout = 10 MINUTES, list/values) + if(!istype(user)) + if(istype(user, /client)) + var/client/C = user + user = C.mob + else + return + var/datum/browser/modal/color_matrix_picker/B = new(user, message, title, button1, button2, button3, stealfocus, timeout, values) + B.open() + B.wait() + return list("button" = B.selectedbutton, "matrix" = B.color_matrix) \ No newline at end of file diff --git a/modular_chomp/code/game/atoms/atoms.dm b/modular_chomp/code/game/atoms/atoms.dm new file mode 100644 index 0000000000..1822d1c5a2 --- /dev/null +++ b/modular_chomp/code/game/atoms/atoms.dm @@ -0,0 +1,55 @@ +/atom + //! Colors + /** + * used to store the different colors on an atom + * + * its inherent color, the colored paint applied on it, special color effect etc... + */ + var/list/atom_colours + +//! ## Atom Colour Priority System +/** + * A System that gives finer control over which atom colour to colour the atom with. + * The "highest priority" one is always displayed as opposed to the default of + * "whichever was set last is displayed" + */ + +/// Adds an instance of colour_type to the atom's atom_colours list +/atom/proc/add_atom_colour(coloration, colour_priority) + if(!atom_colours || !atom_colours.len) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(!coloration) + return + if(colour_priority > atom_colours.len) + return + atom_colours[colour_priority] = coloration + update_atom_colour() + +/// Removes an instance of colour_type from the atom's atom_colours list +/atom/proc/remove_atom_colour(colour_priority, coloration) + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(colour_priority > atom_colours.len) + 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 + atom_colours[colour_priority] = null + update_atom_colour() + +/// Resets the atom's color to null, and then sets it to the highest priority colour available +/atom/proc/update_atom_colour() + 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(L.len) + color = L + return + else if(C) + color = C + return \ No newline at end of file diff --git a/modular_chomp/code/game/machinery/colormate.dm b/modular_chomp/code/game/machinery/colormate.dm new file mode 100644 index 0000000000..3e28f09c16 --- /dev/null +++ b/modular_chomp/code/game/machinery/colormate.dm @@ -0,0 +1,304 @@ +#define COLORMATE_TINT 1 +#define COLORMATE_HSV 2 +#define COLORMATE_MATRIX 3 + +/obj/machinery/gear_painter + name = "Color Mate" + desc = "A machine to give your apparel a fresh new color!" + icon = 'icons/obj/vending.dmi' + icon_state = "colormate" + density = TRUE + anchored = TRUE + var/atom/movable/inserted + var/activecolor = "#FFFFFF" + var/list/color_matrix_last + var/active_mode = COLORMATE_HSV + + var/build_hue = 0 + var/build_sat = 1 + var/build_val = 1 + + /// Allow holder'd mobs + var/allow_mobs = TRUE + /// Minimum lightness for normal mode + var/minimum_normal_lightness = 50 + /// Minimum lightness for matrix mode, tested using 4 test colors of full red, green, blue, white. + var/minimum_matrix_lightness = 75 + /// Minimum matrix tests that must pass for something to be considered a valid color (see above) + var/minimum_matrix_tests = 2 + /// Temporary messages + var/temp + + var/list/allowed_types = list( + /obj/item/clothing, + /obj/item/weapon/storage/backpack, + /obj/item/weapon/storage/belt, + /obj/item/toy + ) + +/obj/machinery/gear_painter/Initialize(mapload) + . = ..() + color_matrix_last = list( + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + 0, 0, 0, + ) + +/obj/machinery/gear_painter/update_icon() + if(panel_open) + icon_state = "colormate_open" + else if(inoperable()) + icon_state = "colormate_off" + else if(inserted) + icon_state = "colormate_active" + else + icon_state = "colormate" + +/obj/machinery/gear_painter/Destroy() + if(inserted) //please i beg you do not drop nulls + inserted.forceMove(drop_location()) + return ..() + +/obj/machinery/gear_painter/attackby(obj/item/I, mob/living/user) + if(inserted) + to_chat(user, SPAN_WARNING("The machine is already loaded.")) + return + if(default_deconstruction_screwdriver(user, I)) + return + if(default_deconstruction_crowbar(user, I)) + return + if(default_unfasten_wrench(user, I, 40)) + return + + if(allow_mobs && istype(I, /obj/item/weapon/holder)) + var/obj/item/holder/H = I + var/mob/victim = H.held_mob + if(!user.attempt_insert_item_for_installation(I, src)) + return + if(!QDELETED(H)) + H.drop_items() + + insert_mob(victim, user) + SStgui.update_uis(src) + + if(is_type_in_list(I, allowed_types) && !inoperable()) + if(!user.attempt_insert_item_for_installation(I, src)) + return + if(QDELETED(I)) + return + user.visible_message(SPAN_NOTICE("[user] inserts [I] into [src]'s receptable.")) + + inserted = I + update_icon() + SStgui.update_uis(src) + + else + return ..() + +/obj/machinery/gear_painter/proc/insert_mob(mob/victim, mob/user) + if(inserted) + return + if(user) + visible_message(SPAN_WARNING("[user] stuffs [victim] into [src]!")) + inserted = victim + inserted.forceMove(src) + +/obj/machinery/gear_painter/AllowDrop() + return FALSE + +// /obj/machinery/gear_painter/handle_atom_del(atom/movable/AM) +// if(AM == inserted) +// inserted = null +// return ..() + +/obj/machinery/gear_painter/AltClick(mob/user) + . = ..() + drop_item() + +/obj/machinery/gear_painter/proc/drop_item() + if(!oview(1,src)) + return + if(!inserted) + return + to_chat(usr, SPAN_NOTICE("You remove [inserted] from [src]")) + inserted.forceMove(drop_location()) + var/mob/living/user = usr + if(istype(user)) + user.put_in_hands(inserted) + inserted = null + update_icon() + SStgui.update_uis(src) + +/obj/machinery/gear_painter/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ColorMate", src.name) + ui.set_autoupdate(FALSE) //This might be a bit intensive, better to not update it every few ticks + ui.open() + +/obj/machinery/gear_painter/ui_data(mob/user) + . = list() + .["activemode"] = active_mode + .["matrixcolors"] = list( + "rr" = color_matrix_last[1], + "rg" = color_matrix_last[2], + "rb" = color_matrix_last[3], + "gr" = color_matrix_last[4], + "gg" = color_matrix_last[5], + "gb" = color_matrix_last[6], + "br" = color_matrix_last[7], + "bg" = color_matrix_last[8], + "bb" = color_matrix_last[9], + "cr" = color_matrix_last[10], + "cg" = color_matrix_last[11], + "cb" = color_matrix_last[12], + ) + .["buildhue"] = build_hue + .["buildsat"] = build_sat + .["buildval"] = build_val + if(temp) + .["temp"] = temp + if(inserted) + .["item"] = list() + .["item"]["name"] = inserted.name + .["item"]["sprite"] = icon2base64(get_flat_icon(inserted,dir=SOUTH,no_anim=TRUE)) + .["item"]["preview"] = icon2base64(build_preview()) + else + .["item"] = null + +/obj/machinery/gear_painter/ui_act(action, params) + . = ..() + if(.) + return + if(inserted) + switch(action) + if("switch_modes") + active_mode = text2num(params["mode"]) + return TRUE + if("choose_color") + var/chosen_color = input(usr, "Choose a color: ", "ColorMate colour picking", activecolor) as color|null + if(chosen_color) + activecolor = chosen_color + return TRUE + if("paint") + do_paint(usr) + temp = "Painted Successfully!" + return TRUE + if("drop") + temp = "" + drop_item() + return TRUE + if("clear") + inserted.remove_atom_colour(FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + temp = "Cleared Successfully!" + return TRUE + if("set_matrix_color") + color_matrix_last[params["color"]] = params["value"] + return TRUE + if("set_hue") + build_hue = clamp(text2num(params["buildhue"]), 0, 360) + return TRUE + if("set_sat") + build_sat = clamp(text2num(params["buildsat"]), -10, 10) + return TRUE + if("set_val") + build_val = clamp(text2num(params["buildval"]), -10, 10) + return TRUE + + +/obj/machinery/gear_painter/proc/do_paint(mob/user) + var/color_to_use + switch(active_mode) + if(COLORMATE_TINT) + color_to_use = activecolor + if(COLORMATE_MATRIX) + color_to_use = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]), + ) + if(COLORMATE_HSV) + color_to_use = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = color_to_use + if(!color_to_use || !check_valid_color(color_to_use, user)) + to_chat(user, SPAN_NOTICE("Invalid color.")) + return FALSE + inserted.add_atom_colour(color_to_use, FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + return TRUE + + +/// Produces the preview image of the item, used in the UI, the way the color is not stacking is a sin. +/obj/machinery/gear_painter/proc/build_preview() + if(inserted) //sanity + var/list/cm + switch(active_mode) + if(COLORMATE_MATRIX) + cm = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]), + ) + if(!check_valid_color(cm, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + if(COLORMATE_TINT) + if(!check_valid_color(activecolor, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + if(COLORMATE_HSV) + cm = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = cm + if(!check_valid_color(cm, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + var/cur_color = inserted.color + inserted.color = null + inserted.color = (active_mode == COLORMATE_TINT ? activecolor : cm) + var/icon/preview = get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + inserted.color = cur_color + temp = "" + + . = preview + +/obj/machinery/gear_painter/proc/check_valid_color(list/cm, mob/user) + if(!islist(cm)) // normal + var/list/HSV = ReadHSV(RGBtoHSV(cm)) + if(HSV[3] < minimum_normal_lightness) + temp = "[cm] is too dark (Minimum lightness: [minimum_normal_lightness])" + return FALSE + return TRUE + else // matrix + // We test using full red, green, blue, and white + // A predefined number of them must pass to be considered valid + var/passed = 0 +#define COLORTEST(thestring, thematrix) passed += (ReadHSV(RGBtoHSV(RGBMatrixTransform(thestring, thematrix)))[3] >= minimum_matrix_lightness) + COLORTEST("FF0000", cm) + COLORTEST("00FF00", cm) + COLORTEST("0000FF", cm) + COLORTEST("FFFFFF", cm) +#undef COLORTEST + if(passed < minimum_matrix_tests) + temp = "Matrix is too dark. (passed [passed] out of [minimum_matrix_tests] required tests. Minimum lightness: [minimum_matrix_lightness])." + return FALSE + return TRUE \ No newline at end of file diff --git a/modular_chomp/code/matrices/color_matrix.dm b/modular_chomp/code/matrices/color_matrix.dm new file mode 100644 index 0000000000..be10a4452c --- /dev/null +++ b/modular_chomp/code/matrices/color_matrix.dm @@ -0,0 +1,240 @@ +///////////////////// +// COLOUR MATRICES // +///////////////////// + +/* Documenting a couple of potentially useful color matrices here to inspire ideas. +// Greyscale - indentical to saturation @ 0 +list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0) + +// Color inversion +list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0) + +// Sepiatone +list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0) +*/ + +/// Does nothing. +/proc/color_matrix_identity() + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + +/** + * Adds/subtracts overall lightness. + * 0 is identity, 1 makes everything white, -1 makes everything black. + */ +/proc/color_matrix_lightness(power) + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0) + +/** + * Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected. + * 1 is identity, 0 is greyscale, >1 oversaturates colors. + */ +/proc/color_matrix_saturation(value) + var/inv = 1 - value + var/R = round(LUMA_R * inv, 0.001) + var/G = round(LUMA_G * inv, 0.001) + var/B = round(LUMA_B * inv, 0.001) + + return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0) + +/** + * Exxagerates or removes colors. + */ +/proc/color_matrix_saturation_percent(percent) + if(percent == 0) + return color_matrix_identity() + percent = clamp(percent, -100, 100) + if(percent > 0) + percent *= 3 + var/x = 1 + percent / 100 + var/inv = 1 - x + var/R = LUMA_R * inv + var/G = LUMA_G * inv + var/B = LUMA_B * inv + + return list(R + x,R,R, G,G + x,G, B,B,B + x) + +/** + * Greyscale matrix. + */ +/proc/color_matrix_greyscale() + return list(LUMA_R, LUMA_R, LUMA_R, LUMA_G, LUMA_G, LUMA_G, LUMA_B, LUMA_B, LUMA_B) + +/** + * Changes distance colors have from rgb(127,127,127) grey. + * 1 is identity. 0 makes everything grey >1 blows out colors and greys. + */ +/proc/color_matrix_contrast(value) + var/add = (1 - value) / 2 + return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0) + +/** + * Exxagerates or removes brightness. + */ +/proc/color_matrix_contrast_percent(percent) + var/static/list/delta_index = list( + 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, + 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, + 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, + 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, + 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, + 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, + 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, + 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8, + 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, + 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8, + 10.0) + percent = clamp(percent, -100, 100) + if(percent == 0) + return color_matrix_identity() + + var/x = 0 + if (percent < 0) + x = 127 + percent / 100 * 127; + else + x = percent % 1 + if(x == 0) + x = delta_index[percent] + else + x = delta_index[percent] * (1-x) + delta_index[percent+1] * x//use linear interpolation for more granularity. + x = x * 127 + 127 + + var/mult = x / 127 + var/add = 0.5 * (127-x) / 255 + return list(mult,0,0, 0,mult,0, 0,0,mult, add,add,add) + +/** + * Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys. + * 0 is identity, 120 moves reds to greens, 240 moves reds to blues. + */ +// +// +/proc/color_matrix_rotate_hue(angle) + var/sin = sin(angle) + var/cos = cos(angle) + var/cos_inv_third = 0.333*(1-cos) + var/sqrt3_sin = sqrt(3)*sin + return list( + round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0, + round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0, + round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0, + 0,0,0,1, + 0,0,0,0, + ) + +/** + * Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites. + * TODO: Need a version that only affects one color (ie shift red to blue but leave greens and blues alone) + */ +/proc/color_matrix_rotation(angle) + if(angle == 0) + return color_matrix_identity() + angle = clamp(angle, -180, 180) + var/cos = cos(angle) + var/sin = sin(angle) + + var/constA = 0.143 + var/constB = 0.140 + var/constC = -0.283 + return list( + LUMA_R + cos * (1-LUMA_R) + sin * -LUMA_R, LUMA_R + cos * -LUMA_R + sin * constA, LUMA_R + cos * -LUMA_R + sin * -(1-LUMA_R), + LUMA_G + cos * -LUMA_G + sin * -LUMA_G, LUMA_G + cos * (1-LUMA_G) + sin * constB, LUMA_G + cos * -LUMA_G + sin * LUMA_G, + LUMA_B + cos * -LUMA_B + sin * (1-LUMA_B), LUMA_B + cos * -LUMA_B + sin * constC, LUMA_B + cos * (1-LUMA_B) + sin * LUMA_B + ) + +/** + * These next three rotate values about one axis only. + * x is the red axis, y is the green axis, z is the blue axis. + */ +/proc/color_matrix_rotate_x(angle) + var/sinval = round(sin(angle), 0.001) + var/cosval = round(cos(angle), 0.001) + return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_y(angle) + var/sinval = round(sin(angle), 0.001) + var/cosval = round(cos(angle), 0.001) + return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_z(angle) + var/sinval = round(sin(angle), 0.001) + var/cosval = round(cos(angle), 0.001) + return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + +/** + * Builds a color matrix that transforms the hue, saturation, and value, all in one operation. + */ +/proc/color_matrix_hsv(hue, saturation, value) + hue = clamp(360 - hue, 0, 360) + + // This is very much a rough approximation of hueshifting. This carries some artifacting, such as negative values that simply shouldn't exist, but it does get the job done, and that's what matters. + var/cos_a = cos(hue) // These have to be inverted from 360, otherwise the hue's inverted + var/sin_a = sin(hue) + var/rot_x = cos_a + (1 - cos_a) / 3 + var/rot_y = (1 - cos_a) / 3 - 0.5774 * sin_a // 0.5774 is sqrt(1/3) + var/rot_z = (1 - cos_a) / 3 + 0.5774 * sin_a + + return list( + round((((1-saturation) * LUMA_R) + (rot_x * saturation)) * value, 0.01), round((((1-saturation) * LUMA_R) + (rot_y * saturation)) * value, 0.01), round((((1-saturation) * LUMA_R) + (rot_z * saturation)) * value, 0.01), + round((((1-saturation) * LUMA_G) + (rot_z * saturation)) * value, 0.01), round((((1-saturation) * LUMA_G) + (rot_x * saturation)) * value, 0.01), round((((1-saturation) * LUMA_G) + (rot_y * saturation)) * value, 0.01), + round((((1-saturation) * LUMA_B) + (rot_y * saturation)) * value, 0.01), round((((1-saturation) * LUMA_B) + (rot_z * saturation)) * value, 0.01), round((((1-saturation) * LUMA_B) + (rot_x * saturation)) * value, 0.01), + 0, 0, 0 + ) + +/** + * Returns a matrix addition of A with B. + */ +/proc/color_matrix_add(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + for(var/value in 1 to 20) + output[value] = A[value] + B[value] + return output + +/** + * Returns a matrix multiplication of A with B. + */ +/proc/color_matrix_multiply(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + var/x = 1 + var/y = 1 + var/offset = 0 + for(y in 1 to 5) + offset = (y-1)*4 + for(x in 1 to 4) + output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001) + return output + +/** + * Assembles a color matrix, defaulting to identity. + */ +/proc/rgb_construct_color_matrix(rr = 1, rg, rb, gr, gg = 1, gb, br, bg, bb = 1, cr, cg, cb) + return list(rr, rg, rb, gr, gg, gb, br, bg, bb, cr, cg, cb) + +/** + * Assembles a color matrix, defaulting to identity. + */ +/proc/rgba_construct_color_matrix(rr = 1, rg, rb, ra, gr, gg = 1, gb, ga, br, bg, bb = 1, ba, ar, ag, ab, aa = 1, cr, cg, cb, ca) + return list(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, cr, cg, cb, ca) + +/** + * Constructs a colored greyscale matrix. + * WARNING: Bad math up ahead. + */ +/proc/rgba_auto_greyscale_matrix(rgba_string) + // process rgb(a) + var/list/L1 = ReadRGB(rgba_string) + ASSERT(L1.len) + if(L1.len == 3) + return rgba_construct_color_matrix(0.39, 0.39, 0.39, 0, 0.5, 0.5, 0.5, 0, 0.11, 0.11, 0.11, 0, 0, 0, 0, 1, max(-0.5, (L1[1] - 255) / 255), max(-0.5, (L1[2] - 255) / 255), max(-0.5, (L1[3] - 255) / 255), 0) + else + // alpha + return rgba_construct_color_matrix(0.39, 0.39, 0.39, 0, 0.5, 0.5, 0.5, 0, 0.11, 0.11, 0.11, 0, 0, 0, 0, 0, max(-0.5, (L1[1] - 255) / 255), max(-0.5, (L1[2] - 255) / 255), max(-0.5, (L1[3] - 255) / 255), L1[4] / 255) \ No newline at end of file diff --git a/vorestation.dme b/vorestation.dme index f8d5ff9b8a..de0d5fc92e 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -61,6 +61,7 @@ #include "code\__defines\items_clothing.dm" #include "code\__defines\lighting.dm" #include "code\__defines\lighting_vr.dm" +#include "code\__defines\lum_ch.dm" #include "code\__defines\machinery.dm" #include "code\__defines\map.dm" #include "code\__defines\materials.dm" @@ -139,6 +140,7 @@ #include "code\_helpers\global_lists_ch.dm" #include "code\_helpers\global_lists_vr.dm" #include "code\_helpers\icons.dm" +#include "code\_helpers\icons_ch.dm" #include "code\_helpers\icons_vr.dm" #include "code\_helpers\lighting.dm" #include "code\_helpers\logging.dm" @@ -899,7 +901,6 @@ #include "code\game\machinery\OpTable.dm" #include "code\game\machinery\overview.dm" #include "code\game\machinery\oxygen_pump.dm" -#include "code\game\machinery\painter_vr.dm" #include "code\game\machinery\partslathe_vr.dm" #include "code\game\machinery\pda_multicaster.dm" #include "code\game\machinery\pointdefense.dm" @@ -4521,19 +4522,25 @@ #include "maps\~map_system\maps.dm" #include "modular_chomp\code\coalesce_ch.dm" #include "modular_chomp\code\global.dm" +#include "modular_chomp\code\_DEFINES\color_priority.dm" +#include "modular_chomp\code\_HELPERS\icons\flatten.dm" +#include "modular_chomp\code\_HELPERS\type2type\color.dm" #include "modular_chomp\code\ATMOSPHERICS\atmospherics.dm" #include "modular_chomp\code\datums\autolathe\arms.dm" #include "modular_chomp\code\datums\autolathe\engineering_ch.dm" #include "modular_chomp\code\datums\autolathe\general_ch.dm" +#include "modular_chomp\code\datums\browser\color_matrix_picker.dm" #include "modular_chomp\code\datums\components\gargoyle.dm" #include "modular_chomp\code\datums\outfits\jobs\noncrew.dm" #include "modular_chomp\code\datums\supplypacks\medical.dm" +#include "modular_chomp\code\game\atoms\atoms.dm" #include "modular_chomp\code\game\dna\dna2.dm" #include "modular_chomp\code\game\jobs\job\department.dm" #include "modular_chomp\code\game\jobs\job\noncrew.dm" #include "modular_chomp\code\game\jobs\job\silicon.dm" #include "modular_chomp\code\game\machinery\airconditioner_ch.dm" #include "modular_chomp\code\game\machinery\autolathe_armory.dm" +#include "modular_chomp\code\game\machinery\colormate.dm" #include "modular_chomp\code\game\objects\items\clockwork\ratvarian_spear.dm" #include "modular_chomp\code\game\objects\structures\desert_planet_structures.dm" #include "modular_chomp\code\game\objects\structures\gargoyle.dm" @@ -4541,6 +4548,7 @@ #include "modular_chomp\code\game\objects\structures\watercloset_ch.dm" #include "modular_chomp\code\game\objects\structures\crate_lockers\largecrate.dm" #include "modular_chomp\code\game\turfs\simulated\outdoors\desert_planet.dm" +#include "modular_chomp\code\matrices\color_matrix.dm" #include "modular_chomp\code\modules\admin\functions\modify_traits.dm" #include "modular_chomp\code\modules\artifice\deadringer.dm" #include "modular_chomp\code\modules\client\preferences.dm"