diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md
index 41fe7592fe..40f9d1de62 100644
--- a/ATTRIBUTIONS.md
+++ b/ATTRIBUTIONS.md
@@ -53,4 +53,17 @@
**File:** `sound/ambience/startup.ogg` and `sound/ambience/shutdown.ogg`
**Title:** Spaceship starup and shutdown
**Creator:** viznoman (https://freesound.org/people/viznoman/sounds/267308/)
-**License:** [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
\ No newline at end of file
+**License:** [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
+
+**File:** `icons/obj/pillows.dmi`
+**Title:** pillows.dmi
+**Creator:** Lamella-0587 (https://github.com/Skyrat-SS13/Skyrat-tg/pull/6740)
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
+
+**File:** `icons/mob/vore/taurs_vr_loaf.dmi`and `icons/mob/vore/taurs_vr.dmi`
+**Title:** Deer tummy
+**Icon-States:** `fatdeer_s` and `fatdeer_markings`
+**Creator:** VerySoft (https://github.com/TS-Rogue-Star/Rogue-Star/pull/153)
+**URL:** [Website](https://rogue-star.net/)
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
+**Notes:** Permission to use assets also given in writing. These new icon-states are modifications of existing icon-states.
\ No newline at end of file
diff --git a/code/ATMOSPHERICS/datum_pipe_network.dm b/code/ATMOSPHERICS/datum_pipe_network.dm
index 20a4dbc159..2306775ad5 100644
--- a/code/ATMOSPHERICS/datum_pipe_network.dm
+++ b/code/ATMOSPHERICS/datum_pipe_network.dm
@@ -1,5 +1,3 @@
-var/global/list/datum/pipe_network/pipe_networks = list() // TODO - Move into SSmachines
-
/datum/pipe_network
var/list/datum/gas_mixture/gases = list() //All of the gas_mixtures continuously connected in this network
var/volume = 0 //caches the total volume for atmos machines to use in gas calculations
diff --git a/code/ATMOSPHERICS/pipes/simple.dm b/code/ATMOSPHERICS/pipes/simple.dm
index 1f427fb151..def462a354 100644
--- a/code/ATMOSPHERICS/pipes/simple.dm
+++ b/code/ATMOSPHERICS/pipes/simple.dm
@@ -96,7 +96,7 @@
node1 = null
if(node2)
node2.disconnect(src)
- node1 = null
+ node2 = null
. = ..()
diff --git a/code/__defines/admin_vr.dm b/code/__defines/admin_vr.dm
index 299b290699..83eb907f3c 100644
--- a/code/__defines/admin_vr.dm
+++ b/code/__defines/admin_vr.dm
@@ -16,4 +16,5 @@
#define MODIFIY_ROBOT_SWAP_MODULE "Swap a Robot Module"
#define MODIFIY_ROBOT_RESET_MODULE "Fully Reset Robot Module"
#define MODIFIY_ROBOT_TOGGLE_ERT "Toggle ERT Module Overwrite"
+#define MODIFIY_ROBOT_TOGGLE_STATION_ACCESS "Toggle All Station Access Codes"
#define MODIFIY_ROBOT_TOGGLE_CENT_ACCESS "Toggle Central Access Codes"
diff --git a/code/__defines/atoms_movable.dm b/code/__defines/atoms_movable.dm
new file mode 100644
index 0000000000..bbcf1eb6d1
--- /dev/null
+++ b/code/__defines/atoms_movable.dm
@@ -0,0 +1,7 @@
+/*
+** Holds defines for code\game\atoms_movable.dm to avoid magic numbers and potential unexpected overwrites down the line
+*/
+
+#define DEFAULT_ICON_SCALE_X 1
+#define DEFAULT_ICON_SCALE_Y 1
+#define DEFAULT_ICON_ROTATION 0
diff --git a/code/__defines/belly_modes_vr.dm b/code/__defines/belly_modes_vr.dm
index c86e66f1c0..35c8425640 100644
--- a/code/__defines/belly_modes_vr.dm
+++ b/code/__defines/belly_modes_vr.dm
@@ -36,3 +36,9 @@
#define MIN_NUTRITION_TO_GAIN 450 // Above this amount you will gain weight
#define MAX_NUTRITION_TO_LOSE 50 // Below this amount you will lose weight
// #define WEIGHT_PER_NUTRITION 0.0285 // Tuned so 1050 (nutrition for average mob) = 30 lbs
+
+// Drain modes
+#define DR_NORMAL "Normal"
+#define DR_SLEEP "Sleep"
+#define DR_FAKE "False Sleep"
+#define DR_WEIGHT "Weight Drain"
diff --git a/code/__defines/chat.dm b/code/__defines/chat.dm
index 681fc8867b..1a9ac03914 100644
--- a/code/__defines/chat.dm
+++ b/code/__defines/chat.dm
@@ -11,6 +11,7 @@
#define MESSAGE_TYPE_SYSTEM "system"
#define MESSAGE_TYPE_LOCALCHAT "localchat"
#define MESSAGE_TYPE_NPCEMOTE "npcemote"
+#define MESSAGE_TYPE_MULTIZCHAT "multizsay"
#define MESSAGE_TYPE_PLOCALCHAT "plocalchat"
#define MESSAGE_TYPE_HIVEMIND "hivemind"
#define MESSAGE_TYPE_RADIO "radio"
diff --git a/code/__defines/color_priority.dm b/code/__defines/color_priority.dm
new file mode 100644
index 0000000000..e88784987f
--- /dev/null
+++ b/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
diff --git a/code/__defines/lum.dm b/code/__defines/lum.dm
new file mode 100644
index 0000000000..942462b573
--- /dev/null
+++ b/code/__defines/lum.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
diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm
index 9d882883a4..eaafb41e90 100644
--- a/code/__defines/machinery.dm
+++ b/code/__defines/machinery.dm
@@ -157,17 +157,17 @@ if (!(DATUM.datum_flags & DF_ISPROCESSING)) {\
#define STOP_PROCESSING_IN_LIST(DATUM, LIST) LIST.Remove(DATUM);DATUM.datum_flags &= ~DF_ISPROCESSING
// Note - I would prefer these be defined machines.dm, but some are used prior in file order. ~Leshana
-#define START_MACHINE_PROCESSING(Datum) START_PROCESSING_IN_LIST(Datum, global.processing_machines)
-#define STOP_MACHINE_PROCESSING(Datum) STOP_PROCESSING_IN_LIST(Datum, global.processing_machines)
+#define START_MACHINE_PROCESSING(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.processing_machines)
+#define STOP_MACHINE_PROCESSING(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.processing_machines)
-#define START_PROCESSING_PIPENET(Datum) START_PROCESSING_IN_LIST(Datum, global.pipe_networks)
-#define STOP_PROCESSING_PIPENET(Datum) STOP_PROCESSING_IN_LIST(Datum, global.pipe_networks)
+#define START_PROCESSING_PIPENET(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.networks)
+#define STOP_PROCESSING_PIPENET(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.networks)
-#define START_PROCESSING_POWERNET(Datum) START_PROCESSING_IN_LIST(Datum, global.powernets)
-#define STOP_PROCESSING_POWERNET(Datum) STOP_PROCESSING_IN_LIST(Datum, global.powernets)
+#define START_PROCESSING_POWERNET(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.powernets)
+#define STOP_PROCESSING_POWERNET(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.powernets)
-#define START_PROCESSING_POWER_OBJECT(Datum) START_PROCESSING_IN_LIST(Datum, global.processing_power_items)
-#define STOP_PROCESSING_POWER_OBJECT(Datum) STOP_PROCESSING_IN_LIST(Datum, global.processing_power_items)
+#define START_PROCESSING_POWER_OBJECT(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.powerobjs)
+#define STOP_PROCESSING_POWER_OBJECT(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.powerobjs)
// Computer login types
#define LOGIN_TYPE_NORMAL 1
diff --git a/code/__defines/misc_vr.dm b/code/__defines/misc_vr.dm
index f3f2b0ef84..b7eb768f6e 100644
--- a/code/__defines/misc_vr.dm
+++ b/code/__defines/misc_vr.dm
@@ -14,6 +14,11 @@
#define VANTAG_KIDNAP "vantag_kidnap"
#define VANTAG_KILL "vantag_kill"
+// ColorMate states
+#define COLORMATE_TINT 1
+#define COLORMATE_HSV 2
+#define COLORMATE_MATRIX 3
+
#define DEPARTMENT_OFFDUTY "Off-Duty"
#define ANNOUNCER_NAME "Facility PA"
diff --git a/code/__defines/text.dm b/code/__defines/text.dm
new file mode 100644
index 0000000000..4b520e5771
--- /dev/null
+++ b/code/__defines/text.dm
@@ -0,0 +1,10 @@
+/**
+ * Holds global defines for use in text input procedures
+ */
+
+
+/**
+ * stuff like `copytext(input, length(input))` will trim the last character of the input,
+ * because DM does it so it copies until the char BEFORE the `end` arg, so we need to bump `end` by 1 in these cases.
+ */
+#define PREVENT_CHARACTER_TRIM_LOSS(integer) (integer + 1)
diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm
index 9fd9e18500..414238fcaa 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)
+ var/list/RGB2 = ReadRGB(rgb2)
// add missing alpha if needed
if(RGB1.len < RGB2.len) RGB1 += 255
diff --git a/code/_helpers/icons/flatten.dm b/code/_helpers/icons/flatten.dm
new file mode 100644
index 0000000000..0435f7b6e1
--- /dev/null
+++ b/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
diff --git a/code/_helpers/icons_procs.dm b/code/_helpers/icons_procs.dm
new file mode 100644
index 0000000000..3ee9bb736a
--- /dev/null
+++ b/code/_helpers/icons_procs.dm
@@ -0,0 +1,665 @@
+/*
+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<