diff --git a/code/controllers/subsystem/greyscale.dm b/code/controllers/subsystem/greyscale.dm index dbbe872b55f..3ff5ee98b43 100644 --- a/code/controllers/subsystem/greyscale.dm +++ b/code/controllers/subsystem/greyscale.dm @@ -27,9 +27,13 @@ SUBSYSTEM_DEF(greyscale) configurations[i].Refresh(TRUE) /datum/controller/subsystem/greyscale/proc/GetColoredIconByType(type, list/colors) + if(!ispath(type, /datum/greyscale_config)) + CRASH("An invalid greyscale configuration was given to `GetColoredIconByType()`: [type]") type = "[type]" if(istype(colors)) // It's the color list format colors = colors.Join() + else if(!istext(colors)) + CRASH("Invalid colors were given to `GetColoredIconByType()`: [colors]") return configurations[type].Generate(colors) /datum/controller/subsystem/greyscale/proc/ParseColorString(color_string) diff --git a/code/datums/greyscale/_greyscale_config.dm b/code/datums/greyscale/_greyscale_config.dm index 58728c7a3b4..60ed15ca6a3 100644 --- a/code/datums/greyscale/_greyscale_config.dm +++ b/code/datums/greyscale/_greyscale_config.dm @@ -1,3 +1,6 @@ +#define MAX_SANE_LAYERS 50 + +/// A datum tying together a greyscale configuration and dmi file. Required for using GAGS and handles the code interactions. /datum/greyscale_config /// User friendly name used in the debug menu var/name @@ -53,17 +56,11 @@ json_config = file(string_json_config) icon_file = file(string_icon_file) - var/icon/source = icon(icon_file) - height = source.Height() - width = source.Width() - - icon_states = list() - var/list/raw = json_decode(file2text(json_config)) ReadIconStateConfiguration(raw) if(!length(icon_states)) - CRASH("The json configuration [DebugName()] is missing any icon_states.") + CRASH("The json configuration [DebugName()] doesn't have any icon states.") icon_cache = list() @@ -76,6 +73,7 @@ /// Takes the json icon state configuration and puts it into a more processed format /datum/greyscale_config/proc/ReadIconStateConfiguration(list/data) + icon_states = list() for(var/state in data) var/list/raw_layers = data[state] if(!length(raw_layers)) @@ -94,7 +92,7 @@ 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"]]") + CRASH("An unknown layer type was specified in the json of greyscale configuration [DebugName()]: [data["layer_type"]]") return new layer_type(icon_file, data) var/list/output = list() for(var/list/group as anything in data) @@ -105,6 +103,10 @@ /// Reads layer configurations to take out some useful overall information /datum/greyscale_config/proc/ReadMetadata() + var/icon/source = icon(icon_file) + height = source.Height() + width = source.Width() + var/list/datum/greyscale_layer/all_layers = list() var/list/to_process = list() for(var/state in icon_states) @@ -117,13 +119,23 @@ else all_layers += current + if(length(all_layers) > MAX_SANE_LAYERS) + stack_trace("[DebugName()] has [length(all_layers)] layers which is larger than the max of [MAX_SANE_LAYERS].") + var/list/color_groups = list() + var/largest_id = 0 for(var/datum/greyscale_layer/layer as anything in all_layers) for(var/id in layer.color_ids) if(!isnum(id)) continue + largest_id = max(id, largest_id) color_groups["[id]"] = TRUE + for(var/i in 1 to largest_id) + if(color_groups["[i]"]) + continue + stack_trace("Color Ids are required to be sequential and start from 1. [DebugName()] has a max id of [largest_id] but is missing [i].") + expected_colors = length(color_groups) /// Actually create the icon and color it in, handles caching @@ -184,7 +196,10 @@ // These are so we can see the result of every step of the process in the preview ui if(render_steps) - render_steps[icon(layer_icon)] = icon(new_icon) + var/list/icon_data = list() + render_steps[icon(layer_icon)] = icon_data + icon_data["config_name"] = name + icon_data["result"] = icon(new_icon) return new_icon /datum/greyscale_config/proc/GenerateDebug(colors) @@ -194,3 +209,5 @@ output["icon"] = GenerateBundle(colors, debug_steps) return output + +#undef MAX_SANE_LAYERS diff --git a/code/datums/greyscale/layer.dm b/code/datums/greyscale/layer.dm index 91b8f29e289..bfcb37ed47a 100644 --- a/code/datums/greyscale/layer.dm +++ b/code/datums/greyscale/layer.dm @@ -35,6 +35,8 @@ processed_colors += i return InternalGenerate(processed_colors, render_steps) +/// Override this to implement layers. +/// The colors var will only contain colors that this layer is configured to use. /datum/greyscale_layer/proc/InternalGenerate(list/colors, list/render_steps) //////////////////////////////////////////////////////// @@ -49,7 +51,8 @@ /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))) + var/list/icon_states = icon_states(icon_file) + if(!(icon_state in icon_states)) 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"]) @@ -77,4 +80,7 @@ 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) - return icon(reference_config.Generate(colors.Join(), render_steps), icon_state) + if(render_steps) + return reference_config.GenerateBundle(colors, render_steps) + else + return reference_config.Generate(colors.Join()) diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm index e9c80821408..0ccc5c4bc54 100644 --- a/code/modules/admin/greyscale_modify_menu.dm +++ b/code/modules/admin/greyscale_modify_menu.dm @@ -146,6 +146,16 @@ split_colors[group] = new_color queue_refresh() + if("random_color") + var/group = text2num(params["color_index"]) + randomize_color(group) + queue_refresh() + + if("random_all_colors") + for(var/i in 1 to length(split_colors)) + randomize_color(i) + queue_refresh() + if("select_icon_state") var/new_icon_state = params["new_icon_state"] if(!config.icon_states[new_icon_state]) @@ -183,6 +193,12 @@ This is highly likely to cause a lag spike for a few seconds."}, for(var/i in 2 to length(raw_colors)) split_colors += "#[raw_colors[i]]" +/datum/greyscale_modify_menu/proc/randomize_color(color_index) + var/new_color = "#" + for(var/i in 1 to 3) + new_color += num2hex(rand(0, 255), 2) + split_colors[color_index] = new_color + /datum/greyscale_modify_menu/proc/queue_refresh() refreshing = TRUE addtimer(CALLBACK(src, .proc/refresh_preview), 1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) @@ -205,24 +221,30 @@ This is highly likely to cause a lag spike for a few seconds."}, icon_state = pick(generated_icon_states) var/image/finished + var/time_spent = TICK_USAGE if(!generate_full_preview) finished = image(config.GenerateBundle(used_colors), icon_state=icon_state) + time_spent = TICK_USAGE - time_spent else var/list/data = config.GenerateDebug(used_colors.Join()) + time_spent = TICK_USAGE - time_spent finished = image(data["icon"], icon_state=icon_state) var/list/steps = list() sprite_data["steps"] = steps for(var/step in data["steps"]) CHECK_TICK - var/image/layer = image(data["steps"][step]) - var/image/result = image(step) + var/list/step_data = data["steps"][step] + var/image/layer = image(step) + var/image/result = step_data["result"] steps += list( list( "layer"=icon2html(layer, user, dir=sprite_dir, sourceonly=TRUE), - "result"=icon2html(result, user, dir=sprite_dir, sourceonly=TRUE) + "result"=icon2html(result, user, dir=sprite_dir, sourceonly=TRUE), + "config_name"=step_data["config_name"] ) ) + sprite_data["time_spent"] = TICK_DELTA_TO_MS(time_spent) sprite_data["finished"] = icon2html(finished, user, dir=sprite_dir, sourceonly=TRUE) refreshing = FALSE diff --git a/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx b/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx index f12e2197eeb..04a3ec1a9b6 100644 --- a/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx +++ b/tgui/packages/tgui/interfaces/GreyscaleModifyMenu.tsx @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Box, Button, ColorBox, Flex, Stack, Icon, Input, LabeledList, Section, Table } from '../components'; +import { Box, Button, ColorBox, Flex, Stack, Icon, Input, LabeledList, Section, Table, Divider } from '../components'; import { Window } from '../layouts'; type ColorEntry = { @@ -11,11 +11,13 @@ type SpriteData = { icon_states: string[]; finished: string; steps: SpriteEntry[]; + time_spent: Number; } type SpriteEntry = { layer: string; result: string; + config_name: string; } type GreyscaleMenuData = { @@ -79,6 +81,11 @@ const ColorDisplay = (props, context) => { +