From bc6c285adb12b792dbd5c7f1f16607fc6ccac365 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sat, 10 Apr 2021 18:59:30 +0200 Subject: [PATCH] [MIRROR] Advanced Greyscale Sprite Generation (#4792) * Advanced Greyscale Sprite Generation (#58112) * Nonfunctional greyscale code * Functional greyscale sprites via filter Probably going to set the icon instead later * Switches to greyscale json config * Adds the reference layer type and converts the other canister types * Working previews * Adds readme * Fixes overlays and breaking * Removes old canister sprites * Removes an unused var * Fixes tgui lints * Removes a bunch of the old canister icon states Yeah I need to fix relabeling as well * Removes some debug sprites * Sorts canister type list and breaks up base shader step * Removes an unnecessary preview hack * Makes prototype canister greyscale * Properly sizes the ui * Fills in the canister map sprite * Adds some more warnings to layers * Makes broken overlay more prominent * Removes a preview var that isn't needed anymore * Cleans up client ref in Destroy * Cleans up the tgui window a bit * Update GreyscaleModifyMenu.tsx * Animates the canister falling over * Removes a commented out line that's no longer needed Co-authored-by: Aleksej Komarov * Advanced Greyscale Sprite Generation Co-authored-by: Emmett Gaines Co-authored-by: Aleksej Komarov --- code/__DEFINES/dcs/signals.dm | 2 + code/__DEFINES/flags.dm | 2 + code/__DEFINES/subsystems.dm | 1 + code/__DEFINES/vv.dm | 1 + code/controllers/subsystem/greyscale.dm | 34 ++ code/datums/greyscale/README.md | 91 +++++ code/datums/greyscale/_greyscale_config.dm | 136 +++++++ code/datums/greyscale/greyscale_configs.dm | 22 ++ .../greyscale/json_configs/canister_base.json | 20 + .../json_configs/canister_default.json | 16 + .../json_configs/canister_double_stripe.json | 29 ++ .../json_configs/canister_hazard.json | 22 ++ .../json_configs/canister_post_effects.json | 15 + .../json_configs/canister_proto.json | 39 ++ .../json_configs/canister_stripe.json | 22 ++ code/datums/greyscale/layer.dm | 74 ++++ code/game/atoms.dm | 22 ++ code/modules/admin/greyscale_modify_menu.dm | 92 +++++ .../admin/view_variables/topic_basic.dm | 6 + .../machinery/portable/canister.dm | 356 +++++++++--------- icons/obj/atmos.dmi | Bin 45288 -> 16432 bytes icons/obj/atmospherics/canisters.dmi | Bin 0 -> 3053 bytes icons/obj/atmospherics/prototype_canister.dmi | Bin 0 -> 1871 bytes tgstation.dme | 5 + .../tgui/interfaces/GreyscaleModifyMenu.tsx | 118 ++++++ 25 files changed, 954 insertions(+), 171 deletions(-) create mode 100644 code/controllers/subsystem/greyscale.dm create mode 100644 code/datums/greyscale/README.md create mode 100644 code/datums/greyscale/_greyscale_config.dm create mode 100644 code/datums/greyscale/greyscale_configs.dm create mode 100644 code/datums/greyscale/json_configs/canister_base.json create mode 100644 code/datums/greyscale/json_configs/canister_default.json create mode 100644 code/datums/greyscale/json_configs/canister_double_stripe.json create mode 100644 code/datums/greyscale/json_configs/canister_hazard.json create mode 100644 code/datums/greyscale/json_configs/canister_post_effects.json create mode 100644 code/datums/greyscale/json_configs/canister_proto.json create mode 100644 code/datums/greyscale/json_configs/canister_stripe.json create mode 100644 code/datums/greyscale/layer.dm create mode 100644 code/modules/admin/greyscale_modify_menu.dm create mode 100644 icons/obj/atmospherics/canisters.dmi create mode 100644 icons/obj/atmospherics/prototype_canister.dmi create mode 100644 tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 2b887226fa8..cbf0d6372e8 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -115,6 +115,8 @@ #define COMSIG_ATOM_UPDATE_ICON_STATE "atom_update_icon_state" ///from base of [/atom/update_overlays]: (list/new_overlays) #define COMSIG_ATOM_UPDATE_OVERLAYS "atom_update_overlays" +///from base of [/atom/update_greyscale]: (list/colors) +#define COMSIG_ATOM_UPDATE_GREYSCALE "atom_update_greyscale" ///from base of [/atom/update_icon]: (signalOut, did_anything) #define COMSIG_ATOM_UPDATED_ICON "atom_updated_icon" ///from base of atom/Entered(): (atom/movable/entering, /atom/oldLoc) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index a9f5860838a..edc657c6db0 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -72,6 +72,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define UPDATE_ICON_STATE (1<<2) /// Update the atom's overlays #define UPDATE_OVERLAYS (1<<3) +/// Update the atom's greyscaling +#define UPDATE_GREYSCALE (1<<4) /// Update the atom's icon #define UPDATE_ICON (UPDATE_ICON_STATE|UPDATE_OVERLAYS) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 66db0f9e7a5..55c4846dd2d 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -110,6 +110,7 @@ #define INIT_ORDER_INPUT 85 #define INIT_ORDER_SOUNDS 83 #define INIT_ORDER_INSTRUMENTS 82 +#define INIT_ORDER_GREYSCALE 81 #define INIT_ORDER_VIS 80 #define INIT_ORDER_ACHIEVEMENTS 77 #define INIT_ORDER_RESEARCH 75 diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index c5f491fc729..1f65488110d 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -79,6 +79,7 @@ // /atom #define VV_HK_MODIFY_TRANSFORM "atom_transform" +#define VV_HK_MODIFY_GREYSCALE "modify_greyscale" #define VV_HK_ADD_REAGENT "addreagent" #define VV_HK_TRIGGER_EMP "empulse" #define VV_HK_TRIGGER_EXPLOSION "explode" diff --git a/code/controllers/subsystem/greyscale.dm b/code/controllers/subsystem/greyscale.dm new file mode 100644 index 00000000000..f7070e04136 --- /dev/null +++ b/code/controllers/subsystem/greyscale.dm @@ -0,0 +1,34 @@ +SUBSYSTEM_DEF(greyscale) + name = "Greyscale" + flags = SS_NO_FIRE + init_order = INIT_ORDER_GREYSCALE + + var/list/datum/greyscale_config/configurations = list() + var/list/datum/greyscale_layer/layer_types = list() + +/datum/controller/subsystem/greyscale/Initialize(start_timeofday) + for(var/datum/greyscale_layer/fake_type as anything in subtypesof(/datum/greyscale_layer)) + layer_types[initial(fake_type.layer_type)] = fake_type + + for(var/greyscale_type in subtypesof(/datum/greyscale_config)) + var/datum/greyscale_config/config = new greyscale_type() + configurations["[greyscale_type]"] = config + + return ..() + +/datum/controller/subsystem/greyscale/proc/RefreshConfigsFromFile() + for(var/i in configurations) + configurations[i].Refresh(TRUE) + +/datum/controller/subsystem/greyscale/proc/GetColoredIconByType(type, list/colors) + type = "[type]" + if(istext(colors)) // It's the color string format for map edits/type values etc + colors = ParseColorString(colors) + return configurations[type].Generate(colors) + +/datum/controller/subsystem/greyscale/proc/ParseColorString(colors) + var/list/split_colors = splittext(colors, "#") + var/list/output = list() + for(var/i in 2 to length(split_colors)) + output += "#[split_colors[i]]" + return output diff --git a/code/datums/greyscale/README.md b/code/datums/greyscale/README.md new file mode 100644 index 00000000000..2e39af452a2 --- /dev/null +++ b/code/datums/greyscale/README.md @@ -0,0 +1,91 @@ +# Greyscale Autogenerated Sprites (GAS) + +If you're wanting to add easy recolors for your sprite then this is the system for you. Features include: + +- Multiple color layers so your sprite can be generated from more than one color. +- Mixed greyscale and colored sprite layers; You can choose to only greyscale a part of the sprite or have premade filters applied to layers. +- Blend modes; Instead of just putting layers of sprites on top of eachother you can use the more advanced blend modes. +- Reusable configurations; You can reference greyscale sprites from within the configuration of another, allowing you to have a bunch of styles with minimal additional configuration. + +## Broad overview + +There are three main parts to GAS that you'll need to be aware of when adding a new greyscale sprite: + +- The json configuration + +All configuration files can be found in [code/datums/greyscale/json_configs](./json_configs) and is where you control how your icons are combined together along with the colors specified from in code. + +- The dmi file + +It contains the sprites that will be used as the basis for the rest of the generation process. You can only have one dmi file specified per configuration but if you want to split up your sprites you can reference other configurations instead. + +- The configuration type + +This is simply some pointers in the code linking together your dmi and the json configuration. + +## Json Configuration File + +The json is made up of some metadata and a list of layers used while creating the sprite. Inner lists are processed as their own chunk before being applied elsewhere, this is useful when you start using more advanced blend modes. Most of the time though you're just going to want a list of icons overlaid on top of eachother. + +```json +{ + "layers": [ + { + "type": "reference", + "reference_type": "/datum/greyscale_config/some_other_config", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + [ + { + "type": "icon_state", + "icon_state": "highlights", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "reference", + "reference_type": "/datum/greyscale_config/sparkle_effect", + "blend_mode": "add" + } + ] + ] +} +``` + +In this example, we start off by creating a sprite specified by a different configuration. The "type" key is required to specify the kind of layer you defining. Once that is done, the next two layers are grouped together, so they will be generated into one sprite before being applied to any sprites outside their group. You can think of it as an order of operations. + +The first of the two in the inner group is an "icon_state", this means that the icon will be retrieved from the associated dmi file using the "icon_state" key. + +The last layer is another reference type. Note that you don't need to give colors to every layer if the layer does not need any colors applied to it. + +"blend_mode" and "color_ids" are special, all layer types have them. The blend mode is what controls how that layer's finished product gets merged together with the rest of the sprite. The color ids control what colors are passed in to the layer. + +## Dmi File + +There are no special requirements from the dmi file to work with this system. You just need to specify the icon file in code and the icon_state in the json configuration. + +## Dm Code + +While the amount of dm code required to make a greyscale sprite was minimized as much as possible, some small amount is required anyway if you want anything to use it. + +As an example: +```c +/datum/greyscale_config/canister + icon_file = 'icons/obj/atmospherics/canisters/default.dmi' + json_config = 'code/datums/greyscale/json_configs/canister_default.json' +``` +And that's all you need to make it usable by other code: + +```c +/obj/machinery/portable_atmospherics/canister + ... + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#ee4242" +``` + +More configurations can be found in [code/datums/greyscale/greyscale_configs.dm](./greyscale_configs.dm) + +## Debugging + +If you're making a new greyscale sprite you sometimes want to be able to see how layers got generated or maybe you're just tweaking some colors. Rather than rebooting the server with every change there is a greyscale modification menu that can be found in the vv dropdown menu for the greyscale object. Here you can change colors and preview the results. diff --git a/code/datums/greyscale/_greyscale_config.dm b/code/datums/greyscale/_greyscale_config.dm new file mode 100644 index 00000000000..bfd012343a5 --- /dev/null +++ b/code/datums/greyscale/_greyscale_config.dm @@ -0,0 +1,136 @@ +/datum/greyscale_config + /// Reference to the json config file + var/json_config + + /// Reference to the dmi file for this config + var/icon_file + + /////////////////////////////////////////////////////////////////////////////////////////// + // Do not set any further vars, the json file specified above is what generates the object + + /// String path to the json file, used for reloading + var/string_json_config + + /// String path to the icon file, used for reloading + var/string_icon_file + + /// Layer objects that the sprite is made up of + var/list/layers + + /// How many colors are expected to be given when building the sprite + var/expected_colors = 0 + + /// Generated icons keyed by their color arguments + var/list/icon_cache + +// There's more sanity checking here than normal because this is designed for spriters to work with +// Sensible error messages that tell you exactly what's wrong is the best way to make this easy to use +/datum/greyscale_config/New() + if(!json_config) + CRASH("Greyscale config object [DebugName()] is missing a json configuration, make sure `json_config` has been assigned a value.") + string_json_config = "[json_config]" + if(!icon_file) + CRASH("Greyscale config object [DebugName()] is missing an icon file, make sure `icon_file` has been assigned a value.") + string_icon_file = "[icon_file]" + + Refresh() + +/datum/greyscale_config/proc/Refresh(loadFromDisk=FALSE) + if(loadFromDisk) + json_config = file(string_json_config) + icon_file = file(string_icon_file) + + var/list/raw = json_decode(file2text(json_config)) + layers = ReadLayersFromJson(raw["layers"]) + if(!length(layers)) + CRASH("The json configuration [DebugName()] is missing any layers.") + + icon_cache = list() + + ReadMetadata() + +/// Gets the name used for debug purposes +/datum/greyscale_config/proc/DebugName() + return "([icon_file]|[json_config])" + +/// Takes the json layers configuration and puts it into a more processed format +/datum/greyscale_config/proc/ReadLayersFromJson(list/data) + var/list/output = ReadLayerGroup(data) + return output[1] + +/datum/greyscale_config/proc/ReadLayerGroup(list/data) + if(!islist(data[1])) + var/layer_type = SSgreyscale.layer_types[data["type"]] + if(!layer_type) + CRASH("An unknown layer type was specified in greyscale configuration json: [data["layer_type"]]") + return new layer_type(icon_file, data) + var/list/output = list() + for(var/list/group as anything in data) + output += ReadLayerGroup(group) + if(length(output)) // Adding lists to lists unwraps the top level so here we are + output = list(output) + return output + +/// Reads layer configurations to take out some useful overall information +/datum/greyscale_config/proc/ReadMetadata() + var/list/datum/greyscale_layer/all_layers = list() + var/list/to_process = list(layers) + while(length(to_process)) + var/current = to_process[length(to_process)] + to_process.len-- + if(islist(current)) + to_process += current + else + all_layers += current + + var/list/color_groups = list() + for(var/datum/greyscale_layer/layer as anything in all_layers) + for(var/id in layer.color_ids) + color_groups["[id]"] = TRUE + + expected_colors = length(color_groups) + +/// Actually create the icon and color it in, handles caching +/datum/greyscale_config/proc/Generate(list/colors) + if(length(colors) != expected_colors) + CRASH("[DebugName()] expected [expected_colors] color arguments but only received [length(colors)]") + var/key = colors.Join("&") + var/icon/new_icon = icon_cache[key] + if(new_icon) + return new_icon + new_icon = icon_cache[key] = GenerateLayerGroup(colors, layers) + return new_icon + +/// Internal recursive proc to handle nested layer groups +/datum/greyscale_config/proc/GenerateLayerGroup(list/colors, list/group, list/render_steps) + var/icon/new_icon + for(var/datum/greyscale_layer/layer as anything in group) + var/icon/layer_icon + if(islist(layer)) + layer_icon = GenerateLayerGroup(colors, layer, render_steps) + layer = layer[1] // When there are multiple layers in a group like this we use the first one's blend mode + else + layer_icon = layer.Generate(colors, render_steps) + + if(!new_icon) + new_icon = layer_icon + else + new_icon.Blend(layer_icon, layer.blend_mode) + + // These are so we can see the result of every step of the process in the preview ui + if(render_steps) + var/icon/new_icon_copy = new(new_icon) + var/icon/layer_icon_copy = new(layer_icon) + render_steps[layer_icon_copy] = icon(new_icon_copy) + return new_icon + +/datum/greyscale_config/proc/GenerateDebug(list/colors) + if(length(colors) != expected_colors) + CRASH("[DebugName()] expected [expected_colors] color arguments but only received [length(colors)]") + + var/list/output = list() + var/list/debug_steps = list() + output["steps"] = debug_steps + + output["icon"] = GenerateLayerGroup(colors, layers, debug_steps) + return output diff --git a/code/datums/greyscale/greyscale_configs.dm b/code/datums/greyscale/greyscale_configs.dm new file mode 100644 index 00000000000..14f9e4ad5d0 --- /dev/null +++ b/code/datums/greyscale/greyscale_configs.dm @@ -0,0 +1,22 @@ +/datum/greyscale_config/canister + icon_file = 'icons/obj/atmospherics/canisters.dmi' + json_config = 'code/datums/greyscale/json_configs/canister_default.json' + +/datum/greyscale_config/canister/base + json_config = 'code/datums/greyscale/json_configs/canister_base.json' + +/datum/greyscale_config/canister/post_effects + json_config = 'code/datums/greyscale/json_configs/canister_post_effects.json' + +/datum/greyscale_config/canister/stripe + json_config = 'code/datums/greyscale/json_configs/canister_stripe.json' + +/datum/greyscale_config/canister/double_stripe + json_config = 'code/datums/greyscale/json_configs/canister_double_stripe.json' + +/datum/greyscale_config/canister/hazard + json_config = 'code/datums/greyscale/json_configs/canister_hazard.json' + +/datum/greyscale_config/prototype_canister + icon_file = 'icons/obj/atmospherics/prototype_canister.dmi' + json_config = 'code/datums/greyscale/json_configs/canister_proto.json' diff --git a/code/datums/greyscale/json_configs/canister_base.json b/code/datums/greyscale/json_configs/canister_base.json new file mode 100644 index 00000000000..c9318e8a26c --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_base.json @@ -0,0 +1,20 @@ +{ + "layers": [ + { + "type": "icon_state", + "icon_state": "base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "add_shader", + "blend_mode": "add" + }, + { + "type": "icon_state", + "icon_state": "multi_shader", + "blend_mode": "multiply" + } + ] +} diff --git a/code/datums/greyscale/json_configs/canister_default.json b/code/datums/greyscale/json_configs/canister_default.json new file mode 100644 index 00000000000..a37d83fc322 --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_default.json @@ -0,0 +1,16 @@ +{ + "layers": [ + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/post_effects", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/canister_double_stripe.json b/code/datums/greyscale/json_configs/canister_double_stripe.json new file mode 100644 index 00000000000..f1dd6410041 --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_double_stripe.json @@ -0,0 +1,29 @@ +{ + "layers": [ + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + [ + { + "type": "icon_state", + "icon_state": "double_stripe", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "double_stripe_shader", + "blend_mode": "subtract" + } + ], + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/post_effects", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/canister_hazard.json b/code/datums/greyscale/json_configs/canister_hazard.json new file mode 100644 index 00000000000..d900035650f --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_hazard.json @@ -0,0 +1,22 @@ +{ + "layers": [ + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "hazard_stripes", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/post_effects", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/canister_post_effects.json b/code/datums/greyscale/json_configs/canister_post_effects.json new file mode 100644 index 00000000000..ee2b550638c --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_post_effects.json @@ -0,0 +1,15 @@ +{ + "layers": [ + { + "type": "icon_state", + "icon_state": "outline", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "lights", + "blend_mode": "overlay" + } + ] +} diff --git a/code/datums/greyscale/json_configs/canister_proto.json b/code/datums/greyscale/json_configs/canister_proto.json new file mode 100644 index 00000000000..f77e9a5ada9 --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_proto.json @@ -0,0 +1,39 @@ +{ + "layers": [ + { + "type": "icon_state", + "icon_state": "can_base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "can_shader", + "blend_mode": "multiply" + }, + { + "type": "icon_state", + "icon_state": "stand", + "blend_mode": "overlay" + }, + { + "type": "icon_state", + "icon_state": "decals", + "blend_mode": "overlay" + }, + [ + { + "type": "icon_state", + "icon_state": "light_base", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "light", + "blend_mode": "overlay", + "color_ids": [ 3 ] + } + ] + ] +} diff --git a/code/datums/greyscale/json_configs/canister_stripe.json b/code/datums/greyscale/json_configs/canister_stripe.json new file mode 100644 index 00000000000..91444eb566d --- /dev/null +++ b/code/datums/greyscale/json_configs/canister_stripe.json @@ -0,0 +1,22 @@ +{ + "layers": [ + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "stripe", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "reference", + "reference_type": "/datum/greyscale_config/canister/post_effects", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/layer.dm b/code/datums/greyscale/layer.dm new file mode 100644 index 00000000000..b7cc1bc1976 --- /dev/null +++ b/code/datums/greyscale/layer.dm @@ -0,0 +1,74 @@ +/datum/greyscale_layer + var/layer_type + var/list/color_ids + var/blend_mode + + var/static/list/blend_modes = list( + "add" = ICON_ADD, + "subtract" = ICON_SUBTRACT, + "multiply" = ICON_MULTIPLY, + "or" = ICON_OR, + "overlay" = ICON_OVERLAY, + "underlay" = ICON_UNDERLAY, + ) + +/datum/greyscale_layer/New(icon_file, list/json_data) + color_ids = json_data["color_ids"] + blend_mode = blend_modes[lowertext(json_data["blend_mode"])] + if(isnull(blend_mode)) + CRASH("Greyscale config for [icon_file] is missing a blend mode on a layer.") + +/// Used to actualy create the layer using the given colors +/// Do not override, use InternalGenerate instead +/datum/greyscale_layer/proc/Generate(list/colors, list/render_steps) + var/list/processed_colors = list() + for(var/i in color_ids) + processed_colors += colors[i] + return InternalGenerate(processed_colors, render_steps) + +/datum/greyscale_layer/proc/InternalGenerate(list/colors) + +//////////////////////////////////////////////////////// +// Subtypes + +/// The most basic greyscale layer; a layer which is created from a single icon_state in the given icon file +/datum/greyscale_layer/icon_state + layer_type = "icon_state" + var/icon/icon + var/color_id + +/datum/greyscale_layer/icon_state/New(icon_file, list/json_data) + . = ..() + var/icon_state = json_data["icon_state"] + if(!(icon_state in icon_states(icon_file))) + CRASH("Configured icon state \[[icon_state]\] was not found in [icon_file]. Double check your json configuration.") + icon = new(icon_file, json_data["icon_state"]) + + if(length(color_ids) > 1) + CRASH("Icon state layers can not have more than one color id") + +/datum/greyscale_layer/icon_state/InternalGenerate(list/colors, list/render_steps) + . = ..() + var/icon/new_icon = icon(icon) + if(length(colors)) + new_icon.Blend(colors[1], ICON_MULTIPLY) + return new_icon + +/// A layer created by using another greyscale icon's configuration +/datum/greyscale_layer/reference + layer_type = "reference" + var/datum/greyscale_config/reference_config + +/datum/greyscale_layer/reference/New(icon_file, list/json_data) + . = ..() + reference_config = SSgreyscale.configurations[json_data["reference_type"]] + if(!reference_config) + CRASH("An unknown greyscale configuration was given to a reference layer: [json_data["reference_type"]]") + +/datum/greyscale_layer/reference/InternalGenerate(list/colors, list/render_steps) + if(render_steps) + // We're debugging + var/list/debug_data = reference_config.GenerateDebug(colors) + render_steps += debug_data["steps"] + return debug_data["icon"] + return reference_config.Generate(colors) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 56456e1e2f3..13e2f159580 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -128,6 +128,11 @@ ///Used for changing icon states for different base sprites. var/base_icon_state + ///The config type to use for greyscaled sprites. Both this and greyscale_colors must be assigned to work. + var/greyscale_config + ///A string of hex format colors to be used by greyscale sprites, ex: "#0054aa#badcff" + var/greyscale_colors + ///Icon-smoothing behavior. var/smoothing_flags = NONE ///What directions this is currently smoothing with. IMPORTANT: This uses the smoothing direction flags as defined in icon_smoothing.dm, instead of the BYOND flags. @@ -695,6 +700,13 @@ update_icon_state() . |= UPDATE_ICON_STATE + if(updates & UPDATE_GREYSCALE) + var/list/colors = update_greyscale() + // Updating the greyscale config in update_greyscale() is fine or we would check this earlier + if(greyscale_config) + icon = SSgreyscale.GetColoredIconByType(greyscale_config, colors) + . |= UPDATE_GREYSCALE + if(updates & UPDATE_OVERLAYS) if(LAZYLEN(managed_vis_overlays)) SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) @@ -715,6 +727,14 @@ SHOULD_CALL_PARENT(TRUE) return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE) +/atom/proc/update_greyscale() + SHOULD_CALL_PARENT(TRUE) + . = list() + var/list/raw_rgb = splittext(greyscale_colors, "#") + for(var/i in 2 to length(raw_rgb)) + . += "#[raw_rgb[i]]" + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_GREYSCALE, .) + /// Updates the overlays of the atom /atom/proc/update_overlays() SHOULD_CALL_PARENT(TRUE) @@ -1171,6 +1191,8 @@ VV_DROPDOWN_OPTION(VV_HK_RADIATE, "Radiate") VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters") VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller") + if(greyscale_colors) + VV_DROPDOWN_OPTION(VV_HK_MODIFY_GREYSCALE, "Modify greyscale colors") /atom/vv_do_topic(list/href_list) . = ..() diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm new file mode 100644 index 00000000000..b8d5c212175 --- /dev/null +++ b/code/modules/admin/greyscale_modify_menu.dm @@ -0,0 +1,92 @@ +/datum/greyscale_modify_menu + var/atom/target + var/client/user + + var/list/split_colors + + var/list/sprite_data + +/datum/greyscale_modify_menu/New(atom/target, client/user) + src.target = target + src.user = user + + ReadColorsFromString(target.greyscale_colors) + + refresh_preview() + + RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/ui_close) + +/datum/greyscale_modify_menu/Destroy() + target = null + user = null + return ..() + +/datum/greyscale_modify_menu/ui_state(mob/user) + return GLOB.admin_state + +/datum/greyscale_modify_menu/ui_close() + qdel(src) + +/datum/greyscale_modify_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "GreyscaleModifyMenu") + ui.open() + +/datum/greyscale_modify_menu/ui_data(mob/user) + var/list/data = list() + var/list/color_data = list() + data["colors"] = color_data + for(var/i in 1 to length(split_colors)) + color_data += list(list( + "index" = i, + "value" = split_colors[i] + )) + + data["sprites"] = sprite_data + return data + +/datum/greyscale_modify_menu/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("recolor") + var/index = text2num(params["color_index"]) + split_colors[index] = lowertext(params["new_color"]) + refresh_preview() + if("recolor_from_string") + ReadColorsFromString(lowertext(params["color_string"])) + refresh_preview() + if("pick_color") + var/group = params["color_index"] + var/new_color = input( + usr, + "Choose color for greyscale color group [group]:", + "Greyscale Modification Menu", + split_colors[group] + ) as color|null + if(new_color) + split_colors[group] = new_color + refresh_preview() + if("apply") + target.greyscale_colors = split_colors.Join() + target.update_appearance() + if("refresh_file") + SSgreyscale.RefreshConfigsFromFile() + refresh_preview() + +/datum/greyscale_modify_menu/proc/ReadColorsFromString(colorString) + var/list/raw_colors = splittext(colorString, "#") + split_colors = list() + for(var/i in 2 to length(raw_colors)) + split_colors += "#[raw_colors[i]]" + +/datum/greyscale_modify_menu/proc/refresh_preview() + var/list/data = SSgreyscale.configurations["[target.greyscale_config]"].GenerateDebug(split_colors) + + sprite_data = list() + var/list/steps = list() + sprite_data["steps"] = steps + for(var/step in data["steps"]) + steps += list(list("layer"=icon2html(data["steps"][step], user, sourceonly=TRUE), "result"=icon2html(step, user, sourceonly=TRUE))) diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index a6becc966cf..0d323e8cf07 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -21,6 +21,7 @@ var/mob/living/L = target if(istype(L)) vv_update_display(target, "real_name", L.real_name || "No real name") + if(href_list[VV_HK_BASIC_CHANGE]) modify_variables(target, target_var, 0) if(href_list[VV_HK_BASIC_MASSEDIT]) @@ -77,6 +78,11 @@ target._AddElement(lst) log_admin("[key_name(usr)] has added [result] [datumname] to [key_name(target)].") message_admins("[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(target)].") + if(href_list[VV_HK_MODIFY_GREYSCALE]) + if(!check_rights(NONE)) + return + var/datum/greyscale_modify_menu/menu = new(target, usr) + menu.ui_interact(usr) if(href_list[VV_HK_CALLPROC]) usr.client.callproc_datum(target) diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 318d6cb74cf..9d703587844 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -38,9 +38,11 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister name = "canister" desc = "A canister for the storage of gas." - icon_state = "yellow" + icon = 'icons/obj/atmospherics/canisters.dmi' + icon_state = "#mapme" + greyscale_config = /datum/greyscale_config/canister/hazard + greyscale_colors = "#ffff00#000000" density = TRUE - base_icon_state = "yellow" //Used to make dealing with breaking the canister less hellish. volume = 1000 armor = list(MELEE = 50, BULLET = 50, LASER = 50, ENERGY = 100, BOMB = 10, BIO = 100, RAD = 100, FIRE = 80, ACID = 50) max_integrity = 250 @@ -48,6 +50,8 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) pressure_resistance = 7 * ONE_ATMOSPHERE req_access = list() + var/icon/canister_overlay_file = 'icons/obj/atmospherics/canisters.dmi' + ///Is the valve open? var/valve_open = FALSE ///Used to log opening and closing of the valve, available on VV @@ -116,168 +120,173 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) if(mode) . += "This canister is Tier [mode]. A sticker on its side says MAX SAFE PRESSURE: [siunit_pressure(initial(pressure_limit), 0)]." -/obj/machinery/portable_atmospherics/canister/nitrogen - name = "Nitrogen canister" - desc = "Nitrogen gas. Reportedly useful for something." - icon_state = "red" - base_icon_state = "red" - gas_type = /datum/gas/nitrogen - -/obj/machinery/portable_atmospherics/canister/oxygen - name = "Oxygen canister" - desc = "Oxygen. Necessary for human life." - icon_state = "blue" - base_icon_state = "blue" - gas_type = /datum/gas/oxygen - -/obj/machinery/portable_atmospherics/canister/carbon_dioxide - name = "Carbon dioxide canister" - desc = "Carbon dioxide. What the fuck is carbon dioxide?" - icon_state = "black" - base_icon_state = "black" - gas_type = /datum/gas/carbon_dioxide - -/obj/machinery/portable_atmospherics/canister/toxins - name = "Plasma canister" - desc = "Plasma gas. The reason YOU are here. Highly toxic." - icon_state = "orange" - base_icon_state = "orange" - gas_type = /datum/gas/plasma - -/obj/machinery/portable_atmospherics/canister/bz - name = "\improper BZ canister" - desc = "BZ, a powerful hallucinogenic nerve agent." - icon_state = "purple" - base_icon_state = "purple" - gas_type = /datum/gas/bz - -/obj/machinery/portable_atmospherics/canister/nitrous_oxide - name = "Nitrous oxide canister" - desc = "Nitrous oxide gas. Known to cause drowsiness." - icon_state = "redws" - base_icon_state = "redws" - gas_type = /datum/gas/nitrous_oxide +// Please keep the canister types sorted +// Basic canister per gas below here /obj/machinery/portable_atmospherics/canister/air name = "Air canister" desc = "Pre-mixed air." - icon_state = "grey" - base_icon_state = "grey" - -/obj/machinery/portable_atmospherics/canister/tritium - name = "Tritium canister" - desc = "Tritium. Inhalation might cause irradiation." - icon_state = "green" - base_icon_state = "green" - gas_type = /datum/gas/tritium - -/obj/machinery/portable_atmospherics/canister/nob - name = "Hyper-noblium canister" - desc = "Hyper-Noblium. More noble than all other gases." - icon_state = "nob" - base_icon_state = "nob" - gas_type = /datum/gas/hypernoblium - -/obj/machinery/portable_atmospherics/canister/nitryl - name = "Nitryl canister" - desc = "Nitryl gas. Feels great 'til the acid eats your lungs." - icon_state = "brown" - base_icon_state = "brown" - gas_type = /datum/gas/nitryl - -/obj/machinery/portable_atmospherics/canister/stimulum - name = "Stimulum canister" - desc = "Stimulum. High energy gas, high energy people." - icon_state = "darkpurple" - base_icon_state = "darkpurple" - gas_type = /datum/gas/stimulum - -/obj/machinery/portable_atmospherics/canister/pluoxium - name = "Pluoxium canister" - desc = "Pluoxium. Like oxygen, but more bang for your buck." - icon_state = "darkblue" - base_icon_state = "darkblue" - gas_type = /datum/gas/pluoxium - -/obj/machinery/portable_atmospherics/canister/water_vapor - name = "Water vapor canister" - desc = "Water Vapor. We get it, you vape." - icon_state = "water_vapor" - base_icon_state = "water_vapor" - gas_type = /datum/gas/water_vapor - filled = 1 - -/obj/machinery/portable_atmospherics/canister/miasma - name = "Miasma canister" - desc = "Miasma. Makes you wish your nose was blocked." - icon_state = "miasma" - base_icon_state = "miasma" - gas_type = /datum/gas/miasma - filled = 1 - -/obj/machinery/portable_atmospherics/canister/freon - name = "Freon canister" - desc = "Freon. Can absorb heat" - icon_state = "freon" - base_icon_state = "freon" - gas_type = /datum/gas/freon - filled = 1 - -/obj/machinery/portable_atmospherics/canister/hydrogen - name = "Hydrogen canister" - desc = "Hydrogen, highly flammable" - icon_state = "h2" - base_icon_state = "h2" - gas_type = /datum/gas/hydrogen - filled = 1 - -/obj/machinery/portable_atmospherics/canister/healium - name = "Healium canister" - desc = "Healium, causes deep sleep" - icon_state = "healium" - base_icon_state = "healium" - gas_type = /datum/gas/healium - filled = 1 - -/obj/machinery/portable_atmospherics/canister/proto_nitrate - name = "Proto Nitrate canister" - desc = "Proto Nitrate, reacts differently with various gases" - icon_state = "proto_nitrate" - base_icon_state = "proto_nitrate" - gas_type = /datum/gas/proto_nitrate - filled = 1 - -/obj/machinery/portable_atmospherics/canister/zauker - name = "Zauker canister" - desc = "Zauker, highly toxic" - icon_state = "zauker" - base_icon_state = "zauker" - gas_type = /datum/gas/zauker - filled = 1 - -/obj/machinery/portable_atmospherics/canister/halon - name = "Halon canister" - desc = "Halon, removes oxygen from high temperature fires and cools down the area" - icon_state = "halon" - base_icon_state = "halon" - gas_type = /datum/gas/halon - filled = 1 - -/obj/machinery/portable_atmospherics/canister/helium - name = "Helium canister" - desc = "Helium, inert gas" - icon_state = "halon" - base_icon_state = "halon" - gas_type = /datum/gas/helium - filled = 1 + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#c6c0b5" /obj/machinery/portable_atmospherics/canister/antinoblium name = "Antinoblium canister" desc = "Antinoblium, we still don't know what it does, but it sells for a lot" - icon_state = "halon" - base_icon_state = "halon" gas_type = /datum/gas/antinoblium filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#9b5d7f#368bff" + +/obj/machinery/portable_atmospherics/canister/bz + name = "\improper BZ canister" + desc = "BZ, a powerful hallucinogenic nerve agent." + gas_type = /datum/gas/bz + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#9b5d7f#d0d2a0" + +/obj/machinery/portable_atmospherics/canister/carbon_dioxide + name = "Carbon dioxide canister" + desc = "Carbon dioxide. What the fuck is carbon dioxide?" + gas_type = /datum/gas/carbon_dioxide + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#4e4c48" + +/obj/machinery/portable_atmospherics/canister/freon + name = "Freon canister" + desc = "Freon. Can absorb heat" + gas_type = /datum/gas/freon + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#6696ee#fefb30" + +/obj/machinery/portable_atmospherics/canister/halon + name = "Halon canister" + desc = "Halon, removes oxygen from high temperature fires and cools down the area" + gas_type = /datum/gas/halon + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#9b5d7f#368bff" + +/obj/machinery/portable_atmospherics/canister/healium + name = "Healium canister" + desc = "Healium, causes deep sleep" + gas_type = /datum/gas/healium + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#009823#ff0e00" + +/obj/machinery/portable_atmospherics/canister/helium + name = "Helium canister" + desc = "Helium, inert gas" + gas_type = /datum/gas/helium + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#9b5d7f#368bff" + +/obj/machinery/portable_atmospherics/canister/hydrogen + name = "Hydrogen canister" + desc = "Hydrogen, highly flammable" + gas_type = /datum/gas/hydrogen + filled = 1 + greyscale_config = /datum/greyscale_config/canister/stripe + greyscale_colors = "#bdc2c0#ffffff" + +/obj/machinery/portable_atmospherics/canister/miasma + name = "Miasma canister" + desc = "Miasma. Makes you wish your nose was blocked." + gas_type = /datum/gas/miasma + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#009823#f7d5d3" + +/obj/machinery/portable_atmospherics/canister/nitrogen + name = "Nitrogen canister" + desc = "Nitrogen gas. Reportedly useful for something." + gas_type = /datum/gas/nitrogen + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#d41010" + +/obj/machinery/portable_atmospherics/canister/nitrous_oxide + name = "Nitrous oxide canister" + desc = "Nitrous oxide gas. Known to cause drowsiness." + gas_type = /datum/gas/nitrous_oxide + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#c63e3b#f7d5d3" + +/obj/machinery/portable_atmospherics/canister/nitryl + name = "Nitryl canister" + desc = "Nitryl gas. Feels great 'til the acid eats your lungs." + gas_type = /datum/gas/nitryl + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#7b4732" + +/obj/machinery/portable_atmospherics/canister/nob + name = "Hyper-noblium canister" + desc = "Hyper-Noblium. More noble than all other gases." + gas_type = /datum/gas/hypernoblium + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#6399fc#b2b2b2" + +/obj/machinery/portable_atmospherics/canister/oxygen + name = "Oxygen canister" + desc = "Oxygen. Necessary for human life." + gas_type = /datum/gas/oxygen + greyscale_config = /datum/greyscale_config/canister/stripe + greyscale_colors = "#2786e5#e8fefe" + +/obj/machinery/portable_atmospherics/canister/pluoxium + name = "Pluoxium canister" + desc = "Pluoxium. Like oxygen, but more bang for your buck." + gas_type = /datum/gas/pluoxium + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#2786e5" + +/obj/machinery/portable_atmospherics/canister/proto_nitrate + name = "Proto Nitrate canister" + desc = "Proto Nitrate, reacts differently with various gases" + gas_type = /datum/gas/proto_nitrate + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#008200#33cc33" + +/obj/machinery/portable_atmospherics/canister/stimulum + name = "Stimulum canister" + desc = "Stimulum. High energy gas, high energy people." + gas_type = /datum/gas/stimulum + greyscale_config = /datum/greyscale_config/canister + greyscale_colors = "#9b5d7f" + +/obj/machinery/portable_atmospherics/canister/toxins + name = "Plasma canister" + desc = "Plasma gas. The reason YOU are here. Highly toxic." + gas_type = /datum/gas/plasma + greyscale_config = /datum/greyscale_config/canister/hazard + greyscale_colors = "#f62800#000000" + +/obj/machinery/portable_atmospherics/canister/tritium + name = "Tritium canister" + desc = "Tritium. Inhalation might cause irradiation." + gas_type = /datum/gas/tritium + greyscale_config = /datum/greyscale_config/canister/hazard + greyscale_colors = "#3fcd40#000000" + +/obj/machinery/portable_atmospherics/canister/water_vapor + name = "Water vapor canister" + desc = "Water Vapor. We get it, you vape." + gas_type = /datum/gas/water_vapor + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#4c4e4d#f7d5d3" + +/obj/machinery/portable_atmospherics/canister/zauker + name = "Zauker canister" + desc = "Zauker, highly toxic" + gas_type = /datum/gas/zauker + filled = 1 + greyscale_config = /datum/greyscale_config/canister/double_stripe + greyscale_colors = "#009a00#006600" + +// Special canisters below here /obj/machinery/portable_atmospherics/canister/fusion_test name = "fusion test canister" @@ -312,13 +321,14 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) /obj/machinery/portable_atmospherics/canister/proto name = "prototype canister" + greyscale_config = /datum/greyscale_config/prototype_canister + greyscale_colors = "#ffffff#a50021#ffffff" + mode = NONE /obj/machinery/portable_atmospherics/canister/proto/default name = "prototype canister" desc = "The best way to fix an atmospheric emergency... or the best way to introduce one." - icon_state = "proto" - base_icon_state = "proto" volume = 5000 max_integrity = 300 temperature_resistance = 2000 + T0C @@ -383,23 +393,24 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) . = ..() var/isBroken = machine_stat & BROKEN ///Function is used to actually set the overlays - . += "tier [mode]-[isBroken]" + if(mode) + . += icon(canister_overlay_file, "tier[mode]") if(isBroken) - return + . += icon(canister_overlay_file, "broken") if(holding) - . += "can-open" + . += icon(canister_overlay_file, "can-open") if(connected_port) - . += "can-connector" + . += icon(canister_overlay_file, "can-connector") switch(air_contents.return_pressure()) if((40 * ONE_ATMOSPHERE) to INFINITY) - . += "can-3" + . += icon(canister_overlay_file, "can-3") if((10 * ONE_ATMOSPHERE) to (40 * ONE_ATMOSPHERE)) - . += "can-2" + . += icon(canister_overlay_file, "can-2") if((5 * ONE_ATMOSPHERE) to (10 * ONE_ATMOSPHERE)) - . += "can-1" + . += icon(canister_overlay_file, "can-1") if((10) to (5 * ONE_ATMOSPHERE)) - . += "can-0" + . += icon(canister_overlay_file, "can-0") /obj/machinery/portable_atmospherics/canister/should_atmos_process(datum/gas_mixture/air, exposed_temperature) @@ -480,15 +491,18 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) holding.forceMove(T) holding = null + animate(src, 0.5 SECONDS, transform=turn(transform, rand(-179, 180)), easing=BOUNCE_EASING) + /obj/machinery/portable_atmospherics/canister/replace_tank(mob/living/user, close_valve) . = ..() - if(.) - if(close_valve) - valve_open = FALSE - update_appearance() - investigate_log("Valve was closed by [key_name(user)].", INVESTIGATE_ATMOS) - else if(valve_open && holding) - investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) + if(!.) + return + if(close_valve) + valve_open = FALSE + update_appearance() + investigate_log("Valve was closed by [key_name(user)].", INVESTIGATE_ATMOS) + else if(valve_open && holding) + investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) /obj/machinery/portable_atmospherics/canister/process_atmos() . = ..() diff --git a/icons/obj/atmos.dmi b/icons/obj/atmos.dmi index c7d60886fcd32e167d201df9a54be3c9dc0a9a2c..ec462c4172984abb3f6cbc6c81a0161aeef1fa95 100644 GIT binary patch literal 16432 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%0DFQ7qshF`$buY|HvVr zZSb)F+k?bsV$XHAUux+1v%%rZQW=s%_%yGSwY5Vc23@*Cid7b9WmJDGC!We@TmGPL zAV1T)eqG!P(UTkqY%Np|yrWYCvl% z-OZIdpPV!&AsyvUT^Im=ELyiw;nb#)r$mbedf+7ul_wcJI*ogQ@x#GvZa_~*jE#-0 zzP-OO-RdQRdftIVdQ+ZPiw>lUh0yy0w2+eaFBHFu>U8gBQz-_z2K5j#{e1yp;dh-@9oy4S-m^&%@sX3K)w>s{onODlvHG-n_Ri@Ukf8`y z7cIHt{fF|z-P;(jFXU|%PkaR7pP;)6VVHQxb2t%^BuKbdlo|lOe)H2aX)@& zV7Db!CFDD*$LrR{*K6zQ%x4@MIXtX2YC`HGcgVx8?CQBu@}0bO4({YHivk^nXfs)X&yb6X)|-I^NUA5 zHzC+Pq$e4gu63YZGqk;|i!1ZJb9<$cRLMahUDkZmN;J2r>B(^}>Uf_9F@8`89LGCMGS0Td=&&;wJ`=t5(#}Ah?!=7#o6+Xp6siB%$n3@2B zh6NFmyZ)QddM$`HaXujsj1bDdu>GbuwAM7uBrmWKVhGbE)fo68!dLO+UWXH{S<9co zM;OO^I%25R4x8GLrV?iMJWP~X(7?Nvgf{`Ce>2kkXkUf*-?U7mKA&IPqK;T(77!3% z0Gz)omzVBS9tz@gG+RV-RzfMg~aq;kk<)47D%dM1IrX_~;Y%fSzv zACFwLwO=3Wwm-J0G^N?+KrkwB?=&2`ir6ZzoBGHCqI27^^g%-jZI%&835@Ih(b4i( zfJ59h)t*HlsJ#s%D*|ZTsomilxDiFj;Y(Zxzb8jiVnBBQKyCR=laWj1+zfJ7z?-eD zGza8|w9?a8Tv>cOc_NP@E&UKT>xCf;O}{#PActB?QNJJu@cQODTchn44#OOGQ8#wZ zVC1avzC?twU+Y%ak&Tt-Ul-+qI#2QryUf<1D4|uX6OR)<@S7b~q)L6DeFR5a&Qtwb z!|eG*ofqCRCZ@k3tJA=NQbJnU%uW@{3)k2GybWpI5uT16&|zgIsP72D=sIoG2`cSe z#ga3JQ-;_fBZ~aoaAyrMT^@`weez=ZACh2{Hej@d0z0qj^h%8z{2F#4er*Qlx`+%j zY&Dd_5jEnx+AKr_*^40&IGNB1&%2e$N}#MY8aaM|8Srp4qp5lc$@R_5qH}XOnDV7( zuxlQxAC{d)O!Ah#yH4y?swwSpgz8~{y@IV%$jULoxW1iS8a|>>(zc*zIuti}$VTDJ z9LRQ#<(_Xs-rl^ZgNiGj8h-5f1^TF$548hmeoYG6C__ZCPKAb8DtMa2^X`%n2!5)( zZ37xTy)7Ssr|U8{iaYfOHak+kjAr!oZzr&RHR-dI) zQv}ExUkg(_07=#ai*WvN@vvMLdg)Sv*nD>Ok&WZmEc-P=ULZ0Emg|ARr*^(w_!LHt zgG??yYIPQJyaRB!u90Tk!w=c3+af0;3l=D2P`;(*{Xt$Yw`6b}KK|VL@%*>#{p?!o zLR1;eh&&1U*?$wz`;h=!LwF{PQk?sP_9m2Z#H{^`L`KF9+`;fojh}&;;p%G*C}BByjRF7;jh7 zDUr?(CPEAo;e_Hk5C%zc?VLk>KH~RX!dn_l7!7>6*;`8ZJLz>Qkw}K%{YdsjU3Plk z`H5~M10GKJFLB}=&TzDaAn_skEzXJvB`zZQ7EfPSmufmIXr}dVo;=}8ubfVcyCCQ3 zi(_wm8fP&%nU^x?N_`+P9P$unCg4WeOQq;uZ0(6j2Hj*4c^G+_szm4BV_)u^w8}g3 zAuNy1f;Te%EL5;50glEi1<(hP7J45M70$lICN9J#t=UM3EB3`m3ju2Adv@kU}d;SJ$tgPyV;sd49FA6!~1?q0_LFR~pU3RhD&@ zL%`8Oejp9W=Ps@_>_iCMpFhc5Jd}??bj(dP$cZjZO*&D7^X-(`K6q9>CEqSrLrfp2 z#r*SmAUd=tB#>*G?~3tFer^9~+VJMu8UaYHmdjsCGN%EZ>|EcwG0C9+U#xKJC(7)K z5WI%l5T1lvk3WH5Q|^QfbOwN7Z!k)%4hB_+An=0Q>~g^W9nR+xm7$11o9z6h`f-`4 z7V;s*>t=ItFd=qX{SlX(?@hxgOV{X}94K-asaS~JoyNI>ucTgz=i`yj%(3OGm2QIE ze=|Yvw|}hxnmgts2OuqE`U?fTHaBICy1TkWx!%pGfBzkD0fz1gj}B-MQ!lyr%w{q9 zyMi$Id3DNtC||WhRJE0|z}-oQYz3lmT#6s#>!aej8Xq@Gt;*D^$h`{JQtB(Y&yXoC zul&D1tp_3lC?X^4J~T;=^9~ah&O0C(o@1k4V6FyT?IqK4@s|rsM|`S8^c)m}{#F_I`fvXUm0_1U_^^R=o1W>J{26rYgaMdE&|JuW3l`MPOJV79i z;r}Ujx{BfV++%a9yC+sR@~tj+Lc1Oc&VK@wm1G*stJr=Kk@L-5y8MqHNAAEbA2SB? z+JQHdD3BNNvbU%7`26TCqI&&kj5t4}qmnl9-6@+#ugOgQsk=XKfCL8qd-r?F*4yVh28}$g{^;v=+ZPOR5y3f3nbe zQhSSCf^MHEj3KNgoh)OF5&(U!2k!2K>FEMRsJu_ebZDx>pO*{m#|{RwdqY?dFZwKr zx~+N-uXnS5M4I#r6XYg@g$=NBA4l%I2Nl`c#`?YI_luNuspiJB4q~pSYell z)hQ}G{S0JTetuUM)ty@yi(|~i@u!zZt1$zJrKXxCVZoXC&k|j8cL{`6Z*vO|T2nbX zA!&fZ0r%);vOHQBolzusF8TUyovg7k#NCit>Hq^O!w0|3kLCy|QG(XN;o+RUHK&|0 zn9wnlrS41`WI3riyr<``?37~6f}`{E1a&j8tmLw$ZgAM6FTX7&Oc;Mn{Mgm3{de6U z0vx^n$Ml~!0<$as5(c8}4aGJ)!wpP~f{gkusXXkSD2kNnnOI+Z_;oO)-7Fbm6v)YE&rq(?8mr0qaYjs=;HS8wZ=6R9_-BSILzF0TBd+&_F^ z-k=ozBxdT*!a{m++X_I#bhGR43@*|qGhuOlt_lDS>^I;SJaZnb`j1{!bl-a#kLLHg zTrVjNUw^fG-wrBqhZ?&VP+LtSQus-f^fXT8CN}4|)o44R%B;~*?zmbm?Z!XefV%X{ zZxjQuF@#p~nX$#^XK^p?oYp^e!DZfs{z=Si_r2g@1$u@pwJDp{I3=aY7{5C{UV#AT zq4(vVWw-Op)Btax}KpdP;g|%h>}nU4Z%C331~o zX=o*ht+lZqUmn+bg5Ku&Haa3lX+7QfYNU~$tU282`k%&KwF-ZDa z!hgB|yJiRkg^(fpPVf(F6{4tU?K?3sp)m_A@qtmW7T8RRmFbD5_CMjd5R3V+8n=pZK2N~IQds{NyPq|q?sm0q zTo$=CRA^X49w0?)w#teVEJqLh82otUn5h}ot_mcU+u_Qmwp?)U;`Al|kUqlPpwD^X z5bcl1=gIBKza5mg3&uAswc$vMFWn<08+&(e5h7v7W+7i?3kWA&A(avI&o|XO^6ETRSdiOY}pz@ zfD@-v4Jn)|eU8e5xvjh}WAQhk0+1ivCAM+GJ5#(!z8SVhtiwmz|hUW zzo*wK@D1rJunmoS&Wc;uFcw>n$B4-bOMCcb%$p(G=H(YZ)Jmu{9$vNOkOyM6yRpR1 zmRtDG&ryaaq098!RXgT16ZII_| zUsAYtKeYs1P?ztOIx3CKR-rGa&QC8=4qA#}_N@A!Z7#!~@Z?f3Vrub67+Ae9IXNpf z+zG9%Ud#TT$k!TOsNGTXR|U3b4Zm@w>~PbIR)6{2x1age>RJvMn`S3Imm*em>8Gj= zuSHJ6DozlSeL0ht!j{CSA&p(?_|J!GrCtuC$-liS~q3fBtvte=`D44_pW`>u&0i z4;t|cOJ0OsnW{+2yTNDt%WvRh>Sq=Dptu3iKLt@pG3_}k_auN}qqTdGVc35^3hPMt z9*>J+)f05meEF5sTi!Bnr&_1}vFE19LJ_j_$(Er^8c;^Hg}C_6cDLRz7%;PPR5WubF^8JHeVD zkGT!tg|Q4Q)HW;2^7BpUieGmwY{zPI0cj6r0XNBrSpc<8P2fzs_8T4IL_FIT)u z`+{Y-1c9@^eH8ff-fG=-WKJ9P#Ie^kIyyR;or*6`$jWur2VjZ@RDX@vTi4(h&+K!m zp(6f|N_Sj;<}!NydHkh;VAB4llfg-0%hl~=a)iF^d_KowFeCl@cz`GmX9G5GxELR( zba-#FfNX_J76qW0fyKqllfa@EkFe%Jp`ip($QjJ`OkF8hMARD`zYX-TF|OxHZQ}5g z%I!`pr*a881R%=TJ>>Ls#Dhuc)3dYEU0WQ{n+tH*GKJ2Dc!t+u`*x0wL_lb0C_i&3 z0|*_h1YK1j8*gAR*7#X4j}Cq#b1aGPYbpI)T6q;SaHiZ4o31w(h1pa-bbxMN=lkomNfS7+r+1n{QBp9@v~PWyxOCk*R!`5 zT&pWnj;P1q?0}6?XuGC~+sbir29Z%vgl6tN+zhl<*$mWRr5XGQXZuzQ)qUVPfU`oj zw@S`YNs&;J_ew2Gr4HKIWY4^PU~ZmOj?rL^*8!WC;^#+3w-NITjt^Av3PLUHjBf0Z z)n0_gD%6r=AAHJ1MlkfS%@j@?M-+iSQM>Fhzp(`lT3qJ6duwaNP$={v26a+&5;A)w zy~-lHU7)Pk9b&`0Jr)=$?kZoqlB^>%x*-j*t=G>@%xg$IL5lfArO`?^(z@4PW>#w* z`GO}nR>jJSW35(DRFnoBgXU)7MDPUUgB(Ztkj2J8pM_fMXRpq8F{u-ZSYazsisX!> zJ7h?QljZMopD#q%d@_!nIv_@$kTT6RDw88Vpjk?G5Qg>aEb7}+;6sUtiPdU7xj5c& z)3&N-w4R4C4!murEU2Laqgw6likYuKZBEa~u#!e5w@`xE#@bay2&O=4MfR4KmrGG7 z_c-A!&Ho>6*>6k*Kkt6`!?Y?tU*(UG0h?y`A;?D1AI!)OQDf9fcr-yuBQ)aD_rMJW zn1sHOlSw*%4}+_4l!UP^8>&Ql>FDQIB;uPSBa zsc;jtkKhL_vIobPw{xlo_|%jNxFbrl(jn&1dKBDiF)-PeV$cv5WGUTP`>u({k}gn= zqs{MxF4NM;O?ySiAkHL3q)(mQe-UNranKdUkd=I@fYig&Cw}#xU}emg`{wn+q_l`y zFyk&q^S^Mi(Q|y1^lUwZdVlk9MVEbm0?bjM?8YMyA*Y(arV>brIG{Kt9bS5778cq5 z!+!&3(Sw2tBjUQ5?Q&Ru1rNcJ$>h~d)pm3j8{R#tT0{Xz_t^&i4 zCZ)xL-P}GE-4=y>&=z7d4L12oA6>PC9U`7LSdg+ z7!Rr z=66mPKKo^|khhn3smmG&B8YO_^G}aCeEAQV+FSp^;?j9IROIyr(w6sSX0g9a94kW3 zntBfw$>E2;|LQ7GHOO2h_)VD29!!1*khIi*7?pyk$i2lC4jL%%&n0;S z?F(83hHF{f;d{8JHN2-)!j`dMQ3%j+dax|!^!r}xhT1%*OBs9hr1C(v0t*nImKM1& zUi1UT9dx>|w>QC)yEE6g1)2(eDWVFaivwb*yaKB4|PHfGp+ z98Lgzjra)GsbI3?fKfDbaBy({;J_vMt1724@n6Sv{L$Z8G+w`hI4_4b_S_*6RC0kH zP18eBk9PuZF1(Xsju}waKt{T*&~;oxv~s{+VY7-M80l9@xpCfNIEGsw)<#1N-P%VW=j~;y= z7}%EVB(6>loEGtNS?VFR@m=|uq&r@>1$$in@OY+w)Q z`Q%cJ%NtZ2@O=G8@Ao|mT6j3Al6LZ)=fV~1xi6(NMrUWECMO?fbi5Q#(>E~*-xp{bvXf(S?9VNd-_Qu9v&W^_<9~5QY{`^-~4xV&{w8j6XEm^2oUfj)lh}LlFsn>QPsXF3xf`yL{lk&;2De)2ElV2 zb>EhlHTQ^^jag3wz0ac6A|aCZF>dzq3mk4Ef3FM2GJN7yLPx`HX^9l3cRt;hXOmze zvajdq8mZ{Y$jG?XAAQPGdBoy?RZu%sBg{QL^=_S-1aa2b*SF=iG6#tK;BYgC%}M{; zrH}sdKj8d$A}ulTKBS}eRlhU%;)<`8jTb-cz6>Ew^APyi@aD!W_4MOlGIJu`9O)Rt zF+>Wwgl^7hFRgly>ZU&Gf?EF2FBbP~?hM1X_wf6?O-A+(yXW5%(bjcDfVE6t&fF?| z!mrc_x5L%_XZs>r7&2oYXl!7>DNUa8q}G}gJ!6|QbJO{F)$8%+&+mc21j478buR7` z6RIe2o~K{l-jE0YU%}0J1agDc+|=~*9GC$j1i{NM`G>4`Z`>95jjQU}^Q0!GIE(Cng?1?Zf%6 z6M*JN1O0IwQJ9;f#L+}N%99J9q;gsFlvzps2ICZOhbKk_Y{%Ou$6nR5KT~%H^84O- zKK`5=e)yV2-Bb)LTRuRqiuNe3dQu+vl~@Y67U|ZMTBIf>Rk)q_`?hFV`Di@;GPH06 zxuMtX^*3mqo*>&l76yz&U6z?o;bR17;;t?&^@^E<>J9 zB;drZ(c+(5odo6Zp9h*B4H<9t-mSgQ{&gF5e7H?=srC{VxNR2hVgEZ#>lJJ|>guk7mTn2Aw`<@llRCd}aqWsgz@%mN4lSJoS1WKiWIoVFDXJLnVYxv@h-F505G`^aH z!$z37_Il4MeXlJlt$wE^*R&65#SgNx*Ws5(g{foq;)ET%r(nsOyDv{Wf+?L>eypMwIgkOe8h3F~z%U!8jRb7*p9AM+*1OJCDiqC!8wuBhwDZ>)Rxa=#ffu+7ir~ zkdl&I0JN>T<2;D-&xvmpzTtj+PHOSG^dnT$t4nDEUzV_}Y?_v?ZV`}cJDj~fQ^yA^ zf-3`Ld{O*_6y388_8u>ez{LcIUJU+z=VZF_Dy7=u!2WaDfcxh=<{ve|#1!|QQFy<| z1CgZkLT)PA1L$TjE(>R&tVVG&;^G}ek36m|*mt=KzIwCzCEJ|PtrSB|A3DU^slpAh zpa*Y7KZc|#aY-}PxG)EvHntTKr1p|C8diRJuzF>j1uh*dVQN@Y4@x&%g`oJ>^Tj0* zgwB0{NR0Vc8(ou*7(!xo;_Ju1?}y;COZA?3i~}*os!U7 ze7Q1aDR9|UQD8LY{L0WKC%-#b?t2LqMpE@YfBrmG!B5(kkdRYCf*wS|1lG_AREkA& za34GTrhNPF7%?uc(MC&|*cCI8lO8s(D!*o(!i`Sm=RZ^aVSjg5NT%ZY!jmCK;d8Z$ zti(hDKto$w)zPtX9F>-qChU-S{qpYRxh<>tGwKa5#>)UvVs{tpi_setkvBWP&V^)A zN#NgH)x>14OwqNV5>DJNSPR#kjz#BumeQUk>gUR$FJ$NCwRD3aSx`j1S_;gu78CH~ za@N+yKqaUsd(G?m`*qArp^)aEXexrC7HfA#aKVURr5lx_{CW!T-4_3m% z4El$KD{6!gCr;#VtHSMQD;qaQ5=W0DahQYE>MxjX)F%k1fR@|$|%V@j}9^%~bAIl$_W8`n8 z&Agp_BPN7%=e2Z;WRg@@jUOZJ0hDOoZu!D$xFt~?R} zaz6Kftsi=EgF9PW+7;M!F5bbYYl5HNrf|b+wP1>1cGmfpy>t}$b>J$O%zKqjUdoPL z+DkZLJ^w4q^0{*PVq>vR#9q+iVukT^Jty?JzUZpZ?58I*SGRS%tW;>R_4FT<&HF`m zZxn=qe6O9+n3b~yTJP7HlaQDJ^JsdFZ__m|D^$nHCLoPT_xL@3SocOudh3c=g{vwm!eDO*beN*>)(>6r-D70a zV@{btUVeU(qH;q5LPCLLz5~B^NLrJh6?`9FMH8~yI(Cipvb(Le2B-FMo2HvOgAy5B zD0J@lMB9iQ+|OK1>fXxf`~8yct!aj-zI~q_lG5JcjdB?Yv=z=_;MEDHn0JIhr?qgM zE&_bPAPn8^wdm{bQ-9x8fW7-L*+ z`;OtoV`2glFiw!7+v|x@IodJzOxt31hR|K$gFs84pyQmix@z-d%Qroz=!ZW(`j_q1 z(AP_dhRU42<2Q}3|F$9Nsj6VFCdxax%+57x{rj@r!P7K_i#O(3x>E-0e+IE`L@spJy$EONGPaOUt%Ar`LlLH!PH-VK_90FmggZxAex9vWKYIb zkFa&2ib&SMgR4F0GBbI^e_Htg|6;Y7@pwe3ZT^;xKORe*LkPPG)3#QXNpi~zr}8lR&FU|KOjAgm;E@+wQ{{q}rZCKD}m z^^_-xEf5YcnRs{@E3p^o3mGx5zHrPYuwRTHkE32#dirMZd1D7vkd$-8j%gAlctD54 z;|X!SV>c0N>jAq}#l8nBDk8#4_wt5>4Tq4>!{dh9$;%W=SQ=j7`DWc~JG+jZcPs<7 zJ%wqfx0n4Wm`iMRFU>q2fL>m1`}rMPtfkZA<*AHc(q~@QSLK81XF-Y{&TxhmBAQ86lvsx=;o-p%0?%WJ3^2452-o_Ki>;Ic7!TV z`&3%A&F)ImBEGS?!Yy zk#Zy!kNi{ML^{hM0C+qOrGhaZYl<#rhwH=6_`ysTc~RTbBx1mm8s?e3{ZH`}eSJYc zF~?;m9R61y=onUGO!Yx&Iv3ALPR#Rrq~?rtI*APbDl^#vmv17v2S^-?`-yV}jEY{j zkrJq+sI3%hkPzVIvHo|orke%$`l4uV{`Z&dxvGEo>zmIz-oH4X&d7NUuMKn2kZh`G z{cA}oWbtjZLu1qjyrWj)^KM`<-%>9Fg~IS!(7(;|Uq6$?7y!Ul9Y5v_6mSxV{%rs8 z8H2APg0x8=q?TYkoRBX2gB{_6=aQbjFpz0Xg#Ve|NP>M%RpQ_$U!%Koc(Lb!B7aTI z`) zTKgdRWOv5gd6rg-i1T=)0rULrj&d+f(4tQSmC_5mm}x6+;(sU(=rm+Q=mSHw_$vt* z{I)!)QHK6~70U;f9oJS38#1d}$F7-A%TJdpUL5;RTJ<>=VsuLlxa{ z33g)hUzaThSPR&OdyPP|zeau}BlJFtkV_5@vx^qSR{M?Hp8T}D7`UUwKQh84E)L`C zj2Oz9Af_eB4q7y?c2sCe5s~{HS+TYHD!|7_Y&+J7JH`>p~r?YJEp`Lv3 z1SQy>bZtNp`9etFyU|oMUJCaj+F7rJ(X240+2gmaq7KYKC^^8JODK8z=}xT@xvkh4 z{nhjs%I88d8p);qk|V1h8sbC!D4DXv-prWZ z#MfdP#OFK3W{v`FW{rIN>L;$CaHaLk?=4~X8%zm`cSc=XHDkXCgMl39SvJGIGG0(T z76~K#Ue`A?-%!YoW-O1%+k4j_eYc>Yd8RpW3L2m4DO>SSs3gE9IX9=BWpZ{Rc(Ukv zu@5Pl8cc#rs6Qr+V6`^Q$WVSdvGe$!pQgFB*`P!axNVXp1)DnFbrH@Ho^GC}OCPpO z*`eKJ*C=6_@ShuK_P_nbt${}}2O&JiH30>Kk^1bTJRD+Z8LcUFHLX#c>ZUJAXe=Qg zFzO+(fQOa2!dCnk{gLO5B}KGG!}q5KPYKT5OjVAa6Eo-Kb~_q8RL#cSqPa)iIh!!q z@<5Hp(C@+9$1+di7gagDc{Ax~S{-@mCObHb+1Xoq>n1w7D>FzzQ2XxV80~nVgGU{- zI3rVmpMQlkpYB6P-mY`pl4kmltIFYi^ZWF^^;>UnRksE?^Z&c><{OUK&_csF_#L$@ R_&)&vnrgcDD^;w5{|}-GV}bwx literal 45288 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 I*f{uq0Xa|6NdN!< diff --git a/icons/obj/atmospherics/canisters.dmi b/icons/obj/atmospherics/canisters.dmi new file mode 100644 index 0000000000000000000000000000000000000000..385914d197908e638825646dbad1e961ed95d518 GIT binary patch literal 3053 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 literal 0 HcmV?d00001 diff --git a/icons/obj/atmospherics/prototype_canister.dmi b/icons/obj/atmospherics/prototype_canister.dmi new file mode 100644 index 0000000000000000000000000000000000000000..fb73aa2ed6d25ff3fa474cf427c56877cd06953f GIT binary patch literal 1871 zcmb`I=~vPT7sr2)idlrmWh|An%!w97t*|sn&BrB|)KSz&#|4>^q)^co1;i!PDKd{4 zq^OMPQ>KwhsiWhPSk$;rE<-L5^dEdel;p8WfuB_5&$437?RRCtd-7>ug4Ey-t_2uEyZ1 z@(8`s(6(4<>~+`RE%HrqI0@Z#7CKm`n5@%eoWdnfJF(!{RnGZAu+Deu5-@f~V2_@Xk_m zY(`CSRYCFLUjyl=%jc>JKbhoG=-o=ve0R1;W$_bhah*z6$rWkH%{`ZiM3@`&`!U~< z26{|ffIu`gb(K+muBxBXQCG(?*m`tVa3Iqhnx0sWaQl5?;y6hBRWVaG-PqWep!mc0 zW74Vl&!3BF9ooie05KK6ek;2mnnZe+j9On>N*+r>Mzwd(&mRTE8*8dJ(@6kra`OB0 z+quiB(R#TRa{jQCus%DBAA1d<358+5+)!vOr^1_vqQ)LHc|~_RNq7Q{1TonR1{B8& zW3$=OSZr5trbqr>;rIg|tr!1=0{+!Oi_3E%W2=WcEf1FTNtWlvfd1D2hVSR&lYFla zXQ#Yc6$k=)dwa!`t=G8A-_iD&m-J!DWK#$PqD*^>Ylv+X65h+4D60lJIXUeq>H=(H z;?UIR(QWF18r;Gx=MVnChn>@w;%5+;53sT_E>xv&l9eqg0`kj9)0O7FxrfE5)uBr- zo?~i8AhE0e?po_JTv%3M^}XPbkMct_SPQpg)Qrb;hdNrv9XI};rSlrWJuMuc-jN8> z^ye_oD=SQOQN~b`dJ_u^iwp^A9|Eg&;@4~Z8z|DgT(_>GKS5L0Ru2l_hsjQc9fsnp zI`b62>3SdoYDVT)T*kokcF6VC=eVg!;SjaFgpaGPq>#VhI8ZVBk5iD_SrwQXd^wt8 zm%*#gByk>h4ekJdo31Yjs1k&MPJP5fey?6dG=bo=t>4$EXiqBW(-|r!^ysCxIK5Zc zfVH??Q=p45n?*6ec5)u1ILWs6&M3YGYM(t*5w4=k?PU``XOYpOCZ1F#$nUF;zi2I* z6y}}26U9$ufGYkpti^LkABmUE;BcW6Ef`h!HkwM^t3Jwy)WyMB)Xo?>iIoVaO( zj6`hN#mC2AXp0ndb%BffE<|bWHb@kG^BDy_?ce|g$ZN{J_9LDtM>)rLcs_d9OxoR))plDj(J=HX^JqG7mDDo!&2BVTV+=@5H;% zH9St)*ZCt@hZ&;y8+tIGU55RGNX^0owFm#16&+nAW~Ny&D{&uNE@DZ|(l#PyskCNZ zET1#C`eV%Q^ln3wPCHtLU@WDrGSF{UDz;(l87Y14V)C1VqTW&x(Y}H#pUV3*%Cl)`$SYbcTTWi z`gNKmd3G|2KO9TBd1{yaJzmzvmv``t`=AN14Mc3lx1k0&4vXcj-PwjsZ5#VOt&@vb zK;zSm3Qzrx#X86U3v+s8#zKRFrZIi{k1b$=pfdffh+buUr6eU!X3T5b04=85oXwYe zN7|0F^b=r$)!4(b!L0`wG=+4 z%6G_1+nhamLERzfk{nBPIn3O&ADn#_^#7tTg9ouL*D;3|)gKb@`j4L**EKrxFV!M@ A`2YX_ literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 0d28f663264..bddc032943e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -339,6 +339,7 @@ #include "code\controllers\subsystem\explosions.dm" #include "code\controllers\subsystem\fire_burning.dm" #include "code\controllers\subsystem\garbage.dm" +#include "code\controllers\subsystem\greyscale.dm" #include "code\controllers\subsystem\icon_smooth.dm" #include "code\controllers\subsystem\id_access.dm" #include "code\controllers\subsystem\idlenpcpool.dm" @@ -719,6 +720,9 @@ #include "code\datums\elements\food\food_trash.dm" #include "code\datums\elements\food\processable.dm" #include "code\datums\elements\food\venue_price.dm" +#include "code\datums\greyscale\_greyscale_config.dm" +#include "code\datums\greyscale\greyscale_configs.dm" +#include "code\datums\greyscale\layer.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" #include "code\datums\helper_datums\icon_snapshot.dm" @@ -1516,6 +1520,7 @@ #include "code\modules\admin\create_object.dm" #include "code\modules\admin\create_turf.dm" #include "code\modules\admin\fun_balloon.dm" +#include "code\modules\admin\greyscale_modify_menu.dm" #include "code\modules\admin\holder2.dm" #include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" diff --git a/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx b/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx new file mode 100644 index 00000000000..64130f08be1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx @@ -0,0 +1,118 @@ +import { useBackend } from '../backend'; +import { Box, Button, ColorBox, Flex, Icon, Input, LabeledList, Section, Stack, Table } from '../components'; +import { Window } from '../layouts'; + +type ColorEntry = { + index: Number; + value: string; +} + +type SpriteData = { + finished: SpriteEntry + steps: Array +} + +type SpriteEntry = { + layer: string + result: string +} + +type GreyscaleMenuData = { + colors: Array; + sprites: SpriteData; +} + +const ColorDisplay = (props, context) => { + const { act, data } = useBackend(context); + const colors = (data.colors || []); + return ( +
+ + + item.value).join('')} + onChange={(_, value) => act("recolor_from_string", { color_string: value })} + /> + + {colors.map(item => ( + + + {" "} +
+ ); +}; + +const PreviewDisplay = (props, context) => { + const { data } = useBackend(context); + return ( +
+ + + Step Layer + Step Result + + {data.sprites.steps.map(item => ( + + + + + ))} +
+
+ ); +}; + +const SingleSprite = (props) => { + const { + source, + } = props; + return ( + + ); +}; + +export const GreyscaleModifyMenu = (props, context) => { + const { act, data } = useBackend(context); + return ( + + + +