From c448104b30352936a78b5f2849e915436cdf38ba Mon Sep 17 00:00:00 2001 From: Emmett Gaines Date: Thu, 20 May 2021 06:39:14 -0400 Subject: [PATCH] Adds greyscale color selection to vending machines (#58901) Yes this is blatant bait to get more things converted. While working on this I fixed item greyscale updates to include their held and worn states, the vending machine ui is now tsx instead of js, icons generated by gags are created with an error state by default, the greyscale color menu defaults to not show you the full debug preview, and the wording in the debug menu is a bit more in-character friendly. Changelog cl add: Greyscale items using GAGS in vending machines can have their colors chosen before vending. Not many things are capable of this yet but expect more to come. /cl --- code/datums/greyscale/_greyscale_config.dm | 40 +-- code/datums/greyscale/greyscale_configs.dm | 45 ++++ code/game/atoms.dm | 10 +- code/game/objects/items.dm | 5 +- code/game/objects/items/tools/screwdriver.dm | 6 +- code/modules/admin/greyscale_modify_menu.dm | 160 +++++++++--- .../admin/view_variables/topic_basic.dm | 3 +- code/modules/vending/_vending.dm | 237 +++++++++++------- icons/Testing/greyscale_error.dmi | Bin 0 -> 983 bytes .../tgui/interfaces/GreyscaleModifyMenu.tsx | 171 +++++++++---- .../interfaces/{Vending.js => Vending.tsx} | 92 ++++++- 11 files changed, 570 insertions(+), 199 deletions(-) create mode 100644 icons/Testing/greyscale_error.dmi rename tgui/packages/tgui/interfaces/{Vending.js => Vending.tsx} (67%) diff --git a/code/datums/greyscale/_greyscale_config.dm b/code/datums/greyscale/_greyscale_config.dm index 71bd56fbcb1..caf27af0139 100644 --- a/code/datums/greyscale/_greyscale_config.dm +++ b/code/datums/greyscale/_greyscale_config.dm @@ -1,4 +1,7 @@ /datum/greyscale_config + /// User friendly name used in the debug menu + var/name + /// Reference to the json config file var/json_config @@ -115,18 +118,31 @@ expected_colors = length(color_groups) /// Actually create the icon and color it in, handles caching -/datum/greyscale_config/proc/Generate(color_string, list/render_steps) +/datum/greyscale_config/proc/Generate(color_string) var/key = color_string var/icon/new_icon = icon_cache[key] - if(new_icon && !render_steps) - var/icon/output = icon(new_icon) - output.Scale(width, height) - return output - var/list/colors = ParseColorString(color_string) + if(new_icon) + return icon(new_icon) + + var/icon/icon_bundle = GenerateBundle(color_string) + + // This block is done like this because generated icons are unable to be scaled before getting added to the rsc + icon_bundle = fcopy_rsc(icon_bundle) + icon_bundle = icon(icon_bundle) + icon_bundle.Scale(width, height) + icon_bundle = fcopy_rsc(icon_bundle) + + icon_cache[key] = icon_bundle + var/icon/output = icon(icon_bundle) + return output + +/// Handles the actual icon manipulation to create the spritesheet +/datum/greyscale_config/proc/GenerateBundle(list/colors, list/render_steps) + if(!istype(colors)) + colors = ParseColorString(colors) if(length(colors) != expected_colors) CRASH("[DebugName()] expected [expected_colors] color arguments but only received [length(colors)]") - // We could do this all in one loop but it's easier to debug this way var/list/generated_icons = list() for(var/icon_state in icon_states) var/icon/generated_icon = GenerateLayerGroup(colors, icon_states[icon_state], render_steps) @@ -135,15 +151,11 @@ generated_icon.GetPixel(1, 1) generated_icons[icon_state] = generated_icon - var/icon/icon_bundle = new + var/icon/icon_bundle = icon('icons/Testing/greyscale_error.dmi') for(var/icon_state in generated_icons) icon_bundle.Insert(generated_icons[icon_state], icon_state) - icon_bundle = fcopy_rsc(icon_bundle) - icon_cache[key] = icon_bundle - var/icon/output = icon(icon_bundle) - output.Scale(width, height) - return output + return icon_bundle /// Internal recursive proc to handle nested layer groups /datum/greyscale_config/proc/GenerateLayerGroup(list/colors, list/group, list/render_steps) @@ -171,7 +183,7 @@ var/list/debug_steps = list() output["steps"] = debug_steps - output["icon"] = Generate(colors, debug_steps) + output["icon"] = GenerateBundle(colors, debug_steps) return output /datum/greyscale_config/proc/ParseColorString(color_string) diff --git a/code/datums/greyscale/greyscale_configs.dm b/code/datums/greyscale/greyscale_configs.dm index 45280f210ab..02604792b4f 100644 --- a/code/datums/greyscale/greyscale_configs.dm +++ b/code/datums/greyscale/greyscale_configs.dm @@ -1,159 +1,204 @@ /datum/greyscale_config/canister + name = "Default Canister" icon_file = 'icons/obj/atmospherics/canisters.dmi' json_config = 'code/datums/greyscale/json_configs/canister_default.json' /datum/greyscale_config/canister/base + name = "Base Canister Style" json_config = 'code/datums/greyscale/json_configs/canister_base.json' /datum/greyscale_config/canister/post_effects + name = "Canister Post-Effects" json_config = 'code/datums/greyscale/json_configs/canister_post_effects.json' /datum/greyscale_config/canister/stripe + name = "Single Striped Canister" json_config = 'code/datums/greyscale/json_configs/canister_stripe.json' /datum/greyscale_config/canister/double_stripe + name = "Double Striped Canister" json_config = 'code/datums/greyscale/json_configs/canister_double_stripe.json' /datum/greyscale_config/canister/hazard + name = "Hazard Striped Canister" json_config = 'code/datums/greyscale/json_configs/canister_hazard.json' /datum/greyscale_config/prototype_canister + name = "Prototype Canister" icon_file = 'icons/obj/atmospherics/prototype_canister.dmi' json_config = 'code/datums/greyscale/json_configs/canister_proto.json' /datum/greyscale_config/carp + name = "Space Carp" icon_file = 'icons/mob/carp.dmi' json_config = 'code/datums/greyscale/json_configs/carp.json' /datum/greyscale_config/carp/disk_mouth + name = "Space Carp, Disk in Mouth" json_config = 'code/datums/greyscale/json_configs/carp_disk_mouth.json' /datum/greyscale_config/wirecutters + name = "Wirecutters" icon_file = 'icons/obj/tools.dmi' json_config = 'code/datums/greyscale/json_configs/wirecutters.json' /datum/greyscale_config/screwdriver + name = "Screwdriver" icon_file = 'icons/obj/tools.dmi' json_config = 'code/datums/greyscale/json_configs/screwdriver.json' /datum/greyscale_config/screwdriver_inhand_left + name = "Held Screwdriver, Left" icon_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json' /datum/greyscale_config/screwdriver_inhand_right + name = "Held Screwdriver, Right" icon_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json' /datum/greyscale_config/screwdriver_belt + name = "Belt Worn Screwdriver" icon_file = 'icons/obj/clothing/belt_overlays.dmi' json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json' /datum/greyscale_config/jumpsuit + name = "Jumpsuit" icon_file = 'icons/obj/clothing/under/color.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit.json' /datum/greyscale_config/jumpsuit_worn + name = "Worn Jumpsuit" icon_file = 'icons/mob/clothing/under/color.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_worn.json' /datum/greyscale_config/jumpsuit_inhand_left + name = "Held Jumpsuit, Left" icon_file = 'icons/mob/inhands/clothing_lefthand.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_inhand.json' /datum/greyscale_config/jumpsuit_inhand_right + name = "Held Jumpsuit, Right" icon_file = 'icons/mob/inhands/clothing_righthand.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_inhand.json' /datum/greyscale_config/jumpsuit_prison + name = "Prison Jumpsuit" icon_file = 'icons/obj/clothing/under/color.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison.json' /datum/greyscale_config/jumpsuit_prison_worn + name = "Worn Prison Jumpsuit" icon_file = 'icons/mob/clothing/under/color.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_worn.json' /datum/greyscale_config/jumpsuit_prison_inhand_left + name = "Held Prison Jumpsuit, Left" icon_file = 'icons/mob/inhands/clothing_lefthand.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_inhand.json' /datum/greyscale_config/jumpsuit_prison_inhand_right + name = "Held Prison Jumpsuit, Right" icon_file = 'icons/mob/inhands/clothing_righthand.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_inhand.json' /datum/greyscale_config/pda + name = "PDA" icon_file = 'icons/obj/pda.dmi' json_config = 'code/datums/greyscale/json_configs/pda.json' /datum/greyscale_config/pda/chaplain + name = "Chaplain PDA" json_config = 'code/datums/greyscale/json_configs/pda_chaplain.json' /datum/greyscale_config/pda/clown + name = "Clown PDA" json_config = 'code/datums/greyscale/json_configs/pda_clown.json' /datum/greyscale_config/pda/head + name = "Head PDA" json_config = 'code/datums/greyscale/json_configs/pda_head.json' /datum/greyscale_config/pda/mime + name = "Mime PDA" json_config = 'code/datums/greyscale/json_configs/pda_mime.json' /datum/greyscale_config/pda/stripe_split + name = "Split Stripe PDA" json_config = 'code/datums/greyscale/json_configs/pda_stripe_split.json' /datum/greyscale_config/pda/stripe_thick + name = "Thick Stripe PDA" json_config = 'code/datums/greyscale/json_configs/pda_stripe_thick.json' /datum/greyscale_config/pda/stripe_thick/head + name = "Head Thick Stripe PDA" json_config = 'code/datums/greyscale/json_configs/pda_stripe_thick_head.json' /datum/greyscale_config/sneakers + name = "Sneakers" icon_file = 'icons/obj/clothing/shoes.dmi' json_config = 'code/datums/greyscale/json_configs/sneakers.json' /datum/greyscale_config/sneakers_worn + name = "Worn Sneakers" icon_file = 'icons/mob/clothing/feet.dmi' json_config = 'code/datums/greyscale/json_configs/sneakers_worn.json' /datum/greyscale_config/sneakers_orange + name = "Orange Sneakers" icon_file = 'icons/obj/clothing/shoes.dmi' json_config = 'code/datums/greyscale/json_configs/sneakers_orange.json' /datum/greyscale_config/sneakers_orange_worn + name = "Worn Orange Sneakers" icon_file = 'icons/mob/clothing/feet.dmi' json_config = 'code/datums/greyscale/json_configs/sneakers_orange_worn.json' /datum/greyscale_config/sneakers_wheelys + name = "Wheeled Sneakers" icon_file = 'icons/obj/clothing/shoes.dmi' json_config = 'code/datums/greyscale/json_configs/sneakers_wheelys.json' /datum/greyscale_config/sneakers_marisa + name = "Marisa Sneakers" icon_file = 'icons/obj/clothing/shoes.dmi' json_config = 'code/datums/greyscale/json_configs/sneakers_marisa.json' /datum/greyscale_config/circuit + name = "Circuit Board" icon_file = 'icons/obj/module.dmi' json_config = 'code/datums/greyscale/json_configs/circuit.json' /datum/greyscale_config/sombrero + name = "Sombrero" icon_file = 'icons/obj/clothing/head/sombrero.dmi' json_config = 'code/datums/greyscale/json_configs/sombrero.json' /datum/greyscale_config/sombrero/base + name = "Base Sombrero Style" json_config = 'code/datums/greyscale/json_configs/sombrero_base.json' /datum/greyscale_config/sombrero/lefthand + name = "Held Sombrero, Left" json_config = 'code/datums/greyscale/json_configs/sombrero_lefthand.json' /datum/greyscale_config/sombrero/base_lefthand + name = "Base Held Sombrero Style, Left" json_config = 'code/datums/greyscale/json_configs/sombrero_base_lefthand.json' /datum/greyscale_config/sombrero/righthand + name = "Held Sombrero, Right" json_config = 'code/datums/greyscale/json_configs/sombrero_righthand.json' /datum/greyscale_config/sombrero/base_righthand + name = "Base Held Sombrero Style, Right" json_config = 'code/datums/greyscale/json_configs/sombrero_base_righthand.json' /datum/greyscale_config/sombrero/worn + name = "Worn Sombrero" json_config = 'code/datums/greyscale/json_configs/sombrero_worn.json' /datum/greyscale_config/sombrero/base_worn + name = "Base Worn Sombrero Style" json_config = 'code/datums/greyscale/json_configs/sombrero_base_worn.json' diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 4c0d3f13608..c255ca562f5 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -223,7 +223,8 @@ if(loc) SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc` - update_greyscale() + if(greyscale_config && greyscale_colors) + update_greyscale() //atom color stuff if(color) @@ -737,7 +738,7 @@ greyscale_colors = colors if(!greyscale_config) return - if(update) + if(update && greyscale_config && greyscale_colors) update_greyscale() /// Checks if the greyscale config given is different and if so causes a greyscale icon update @@ -745,13 +746,12 @@ if(greyscale_config == new_config) return greyscale_config = new_config - if(update) + if(update && greyscale_config && greyscale_colors) update_greyscale() /// Checks if this atom uses the GAS system and if so updates the icon /atom/proc/update_greyscale() - if(greyscale_config && greyscale_colors) - icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors) + icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors) /** * An atom we are buckled or is contained within us has tried to move diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index e5c0c4a0018..76a3eabf9a2 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -207,7 +207,6 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e for(var/path in actions_types) new path(src) actions_types = null - update_item_greyscale() if(force_string) item_flags |= FORCE_STRING_OVERRIDE @@ -255,7 +254,8 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e return /// Checks if this atom uses the GAS system and if so updates the worn and inhand icons -/obj/item/proc/update_item_greyscale() +/obj/item/update_greyscale() + . = ..() if(!greyscale_colors) return if(greyscale_config_worn) @@ -264,7 +264,6 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e lefthand_file = SSgreyscale.GetColoredIconByType(greyscale_config_inhand_left, greyscale_colors) if(greyscale_config_inhand_right) righthand_file = SSgreyscale.GetColoredIconByType(greyscale_config_inhand_right, greyscale_colors) - return /obj/item/verb/move_to_top() set name = "Move To Top" diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm index 51ca8958907..e813a6f2bfd 100644 --- a/code/game/objects/items/tools/screwdriver.dm +++ b/code/game/objects/items/tools/screwdriver.dm @@ -25,6 +25,9 @@ drop_sound = 'sound/items/handling/screwdriver_drop.ogg' pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg' sharpness = SHARP_POINTY + greyscale_config = /datum/greyscale_config/screwdriver + greyscale_config_inhand_left = /datum/greyscale_config/screwdriver_inhand_left + greyscale_config_inhand_right = /datum/greyscale_config/screwdriver_inhand_right /// If the item should be assigned a random color var/random_color = TRUE /// List of possible random colors @@ -46,12 +49,9 @@ /obj/item/screwdriver/Initialize() if(random_color) - set_greyscale_config(/datum/greyscale_config/screwdriver) var/our_color = pick(screwdriver_colors) set_greyscale_colors(list(screwdriver_colors[our_color])) inhand_icon_state = null - lefthand_file = SSgreyscale.GetColoredIconByType(/datum/greyscale_config/screwdriver_inhand_left, greyscale_colors) - righthand_file = SSgreyscale.GetColoredIconByType(/datum/greyscale_config/screwdriver_inhand_right, greyscale_colors) colored_belt_appearance = mutable_appearance(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/screwdriver_belt, greyscale_colors)) . = ..() AddElement(/datum/element/eyestab) diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm index 182493e9216..2f7d4df7894 100644 --- a/code/modules/admin/greyscale_modify_menu.dm +++ b/code/modules/admin/greyscale_modify_menu.dm @@ -1,31 +1,68 @@ +/// The controller for the ui in charge of all runtime greyscale configuration/debug. +/// If `Unlock()` is not called the menu is safe for players to use. /datum/greyscale_modify_menu + /// The "owner" object of this menu, is usually the greyscale object being edited but that can be changed for specific uses of this menu var/atom/target + /// The client that opened this menu var/client/user + /// A keyed list of allowed configs in the form niceName:typepath + var/list/allowed_configs + + /// A callback to control what happens when the user presses apply. Used mainly for if you want the menu to be used outside of vv. + var/datum/callback/apply_callback + + /// The current config being previewed var/datum/greyscale_config/config + /// A list of colors currently selected var/list/split_colors + /// Collection of data for tgui to use in displaying everything var/list/sprite_data + /// The sprite dir currently being shown var/sprite_dir = SOUTH + /// The sprite icon state currently being shown + var/icon_state + /// Whether the full preview should be generated, with this FALSE only the final sprite is shown instead of all steps. + var/generate_full_preview = FALSE -/datum/greyscale_modify_menu/New(atom/target, client/user) + /// Whether the menu is in the middle of refreshing the preview + var/refreshing = TRUE + + /// Whether the menu is currently locked down to prevent abuse from players. Currently is only unlocked when opened from vv. + var/unlocked = FALSE + +/datum/greyscale_modify_menu/New(atom/target, client/user, list/allowed_configs, datum/callback/apply_callback, starting_icon_state="", starting_config, starting_colors) src.target = target src.user = user + src.apply_callback = apply_callback || CALLBACK(src, .proc/DefaultApply) + icon_state = starting_icon_state - config = SSgreyscale.configurations["[target.greyscale_config]"] - ReadColorsFromString(target.greyscale_colors) + var/current_config = "[starting_config]" || "[target?.greyscale_config]" + config = SSgreyscale.configurations[current_config] + if(!(current_config in allowed_configs)) + config = SSgreyscale.configurations["[allowed_configs[pick(allowed_configs)]]"] + + var/list/config_choices = list() + for(var/config_string in allowed_configs) + var/datum/greyscale_config/config = text2path("[config_string]") + config_choices[initial(config.name)] = config_string + src.allowed_configs = config_choices + + ReadColorsFromString(starting_colors || target?.greyscale_colors) + + if(target) + RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/ui_close) 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 + return GLOB.always_state /datum/greyscale_modify_menu/ui_close() qdel(src) @@ -38,7 +75,7 @@ /datum/greyscale_modify_menu/ui_data(mob/user) var/list/data = list() - data["greyscale_config"] = "[config.type]" + data["greyscale_config"] = "[config.name]" var/list/color_data = list() data["colors"] = color_data @@ -48,7 +85,11 @@ "value" = split_colors[i] )) + data["generate_full_preview"] = generate_full_preview + data["unlocked"] = unlocked + data["refreshing"] = refreshing data["sprites_dir"] = dir2text(sprite_dir) + data["icon_state"] = icon_state data["sprites"] = sprite_data return data @@ -63,26 +104,35 @@ "Choose a new greyscale configuration to use", "Greyscale Modification Menu", "[config.type]" - ) as anything in SSgreyscale.configurations + ) as anything in allowed_configs + new_config = allowed_configs[new_config] new_config = SSgreyscale.configurations[new_config] if(!isnull(new_config) && config != new_config) config = new_config - refresh_preview() + queue_refresh() if("load_config_from_string") + if(!(params["config_string"] in allowed_configs)) + return var/datum/greyscale_config/new_config = SSgreyscale.configurations[params["config_string"]] if(!isnull(new_config) && config != new_config) config = new_config - refresh_preview() + queue_refresh() + + if("toggle_full_preview") + if(!generate_full_preview && !unlocked) + return + generate_full_preview = !generate_full_preview + queue_refresh() if("recolor") var/index = text2num(params["color_index"]) split_colors[index] = lowertext(params["new_color"]) - refresh_preview() + queue_refresh() if("recolor_from_string") ReadColorsFromString(lowertext(params["color_string"])) - refresh_preview() + queue_refresh() if("pick_color") var/group = params["color_index"] @@ -94,20 +144,38 @@ ) as color|null if(new_color) split_colors[group] = new_color - refresh_preview() + queue_refresh() + + if("select_icon_state") + var/new_icon_state = params["new_icon_state"] + if(!config.icon_states[new_icon_state]) + return + icon_state = new_icon_state + queue_refresh() if("apply") - target.set_greyscale_config(config.type, update=FALSE) - target.greyscale_colors = "" // We do this to force an update, in some cases it will think nothing changed when it should be refreshing - target.set_greyscale_colors(split_colors) + apply_callback.Invoke(src) if("refresh_file") + if(!unlocked) + return + if(length(GLOB.player_list) > 1) + var/check = alert( + user, +{"Other players are connected to the server, are you sure you want to refresh all greyscale configurations?\n +This is highly likely to cause a lag spike for a few seconds."}, + "Refresh Greyscale Configurations", + "Yes", + "Cancel" + ) + if(check != "Yes") + return SSgreyscale.RefreshConfigsFromFile() - refresh_preview() + queue_refresh() if("change_dir") sprite_dir = text2dir(params["new_sprite_dir"]) - refresh_preview() + queue_refresh() /datum/greyscale_modify_menu/proc/ReadColorsFromString(colorString) var/list/raw_colors = splittext(colorString, "#") @@ -115,22 +183,54 @@ for(var/i in 2 to length(raw_colors)) split_colors += "#[raw_colors[i]]" +/datum/greyscale_modify_menu/proc/queue_refresh() + refreshing = TRUE + addtimer(CALLBACK(src, .proc/refresh_preview), 1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + /datum/greyscale_modify_menu/proc/refresh_preview() for(var/i in length(split_colors) + 1 to config.expected_colors) split_colors += rgb(100, 100, 100) var/list/used_colors = split_colors.Copy(1, config.expected_colors+1) - var/list/data = config.GenerateDebug(used_colors.Join()) sprite_data = list() - 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) - steps += list( - list( - "layer"=icon2html(layer, user, dir=sprite_dir, sourceonly=TRUE), - "result"=icon2html(result, user, dir=sprite_dir, sourceonly=TRUE) + + var/list/generated_icon_states = list() + for(var/state in config.icon_states) + generated_icon_states += state // We don't want the values from this keyed list + sprite_data["icon_states"] = generated_icon_states + + if(!(icon_state in generated_icon_states)) + icon_state = target.icon_state + if(!(icon_state in generated_icon_states)) + icon_state = pick(generated_icon_states) + + var/image/finished + if(!generate_full_preview) + finished = image(config.GenerateBundle(used_colors), icon_state=icon_state) + else + var/list/data = config.GenerateDebug(used_colors.Join()) + 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) + steps += list( + list( + "layer"=icon2html(layer, user, dir=sprite_dir, sourceonly=TRUE), + "result"=icon2html(result, user, dir=sprite_dir, sourceonly=TRUE) + ) ) - ) + + sprite_data["finished"] = icon2html(finished, user, dir=sprite_dir, sourceonly=TRUE) + refreshing = FALSE + +/datum/greyscale_modify_menu/proc/Unlock() + allowed_configs = SSgreyscale.configurations + unlocked = TRUE + +/datum/greyscale_modify_menu/proc/DefaultApply() + target.set_greyscale_config(config.type, update=FALSE) + target.greyscale_colors = "" // We do this to force an update, in some cases it will think nothing changed when it should be refreshing + target.set_greyscale_colors(split_colors) diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 0d323e8cf07..4baa36c05ab 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -81,7 +81,8 @@ if(href_list[VV_HK_MODIFY_GREYSCALE]) if(!check_rights(NONE)) return - var/datum/greyscale_modify_menu/menu = new(target, usr) + var/datum/greyscale_modify_menu/menu = new(target, usr, SSgreyscale.configurations) + menu.Unlock() menu.ui_interact(usr) if(href_list[VV_HK_CALLPROC]) usr.client.callproc_datum(target) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index ffda3d4fa0c..418883c2af6 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -36,6 +36,8 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C var/custom_premium_price ///Whether spessmen with an ID with an age below AGE_MINOR (20 by default) can buy this item var/age_restricted = FALSE + ///Whether the product can be recolored by the GAGS system + var/colorable /** * # vending machines @@ -323,6 +325,7 @@ GLOBAL_LIST_EMPTY(vending_products) R.custom_price = round(initial(temp.custom_price) * SSeconomy.inflation_value()) R.custom_premium_price = round(initial(temp.custom_premium_price) * SSeconomy.inflation_value()) R.age_restricted = initial(temp.age_restricted) + R.colorable = !!(initial(temp.greyscale_config) && initial(temp.greyscale_colors)) recordlist += R /** @@ -791,7 +794,12 @@ GLOBAL_LIST_EMPTY(vending_products) .["user"]["department"] = "No Department" .["stock"] = list() for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records) - .["stock"][R.name] = R.amount + var/list/product_data = list( + name = R.name, + amount = R.amount, + colorable = R.colorable, + ) + .["stock"][R.name] = product_data .["extended_inventory"] = extended_inventory /obj/machinery/vending/ui_act(action, params) @@ -800,96 +808,145 @@ GLOBAL_LIST_EMPTY(vending_products) return switch(action) if("vend") - . = TRUE - if(!vend_ready) - return - if(panel_open) - to_chat(usr, "The vending machine cannot dispense products while its service panel is open!") - return - vend_ready = FALSE //One thing at a time!! - var/datum/data/vending_product/R = locate(params["ref"]) - var/list/record_to_check = product_records + coin_records - if(extended_inventory) - record_to_check = product_records + coin_records + hidden_records - if(!R || !istype(R) || !R.product_path) - vend_ready = TRUE - return - var/price_to_use = default_price - if(R.custom_price) - price_to_use = R.custom_price - if(R in hidden_records) - if(!extended_inventory) - vend_ready = TRUE - return - else if (!(R in record_to_check)) - vend_ready = TRUE - message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") - return - if (R.amount <= 0) - say("Sold out of [R.name].") - flick(icon_deny,src) - vend_ready = TRUE - return - if(onstation) - var/obj/item/card/id/C - if(isliving(usr)) - var/mob/living/L = usr - C = L.get_idcard(TRUE) - if(!C) - say("No card found.") - flick(icon_deny,src) - vend_ready = TRUE - return - else if (!C.registered_account) - say("No account found.") - flick(icon_deny,src) - vend_ready = TRUE - return - else if(!C.registered_account.account_job) - say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.") - flick(icon_deny, src) - vend_ready = TRUE - return - else if(age_restrictions && R.age_restricted && (!C.registered_age || C.registered_age < AGE_MINOR)) - say("You are not of legal age to purchase [R.name].") - if(!(usr in GLOB.narcd_underages)) - Radio.set_frequency(FREQ_SECURITY) - Radio.talk_into(src, "SECURITY ALERT: Underaged crewmember [usr] recorded attempting to purchase [R.name] in [get_area(src)]. Please watch for substance abuse.", FREQ_SECURITY) - GLOB.narcd_underages += usr - flick(icon_deny,src) - vend_ready = TRUE - return - var/datum/bank_account/account = C.registered_account - if(account.account_job && account.account_job.paycheck_department == payment_department) - price_to_use = max(round(price_to_use * VENDING_DISCOUNT), 1) //No longer free, but signifigantly cheaper. - if(coin_records.Find(R) || hidden_records.Find(R)) - price_to_use = R.custom_premium_price ? R.custom_premium_price : extra_price - if(price_to_use && !account.adjust_money(-price_to_use)) - say("You do not possess the funds to purchase [R.name].") - flick(icon_deny,src) - vend_ready = TRUE - return - var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department) - if(D) - D.adjust_money(price_to_use) - SSblackbox.record_feedback("amount", "vending_spent", price_to_use) - log_econ("[price_to_use] credits were inserted into [src] by [D.account_holder] to buy [R].") - if(last_shopper != usr || purchase_message_cooldown < world.time) - say("Thank you for shopping with [src]!") - purchase_message_cooldown = world.time + 5 SECONDS - last_shopper = usr - use_power(5) - if(icon_vend) //Show the vending animation if needed - flick(icon_vend,src) - playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) - var/obj/item/vended_item = new R.product_path(get_turf(src)) - R.amount-- - if(usr.CanReach(src) && usr.put_in_hands(vended_item)) - to_chat(usr, "You take [R.name] out of the slot.") - else - to_chat(usr, "[capitalize(R.name)] falls onto the floor!") - SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) + . = vend(params) + if("select_colors") + . = select_colors(params) + +/obj/machinery/vending/proc/can_vend(user, silent=FALSE) + . = FALSE + if(!vend_ready) + return + if(panel_open) + to_chat(user, "The vending machine cannot dispense products while its service panel is open!") + return + return TRUE + +/obj/machinery/vending/proc/select_colors(list/params) + . = TRUE + if(!can_vend(usr)) + return + var/datum/data/vending_product/product = locate(params["ref"]) + var/atom/fake_atom = product.product_path + + var/list/allowed_configs = list() + var/config = initial(fake_atom.greyscale_config) + if(!config) + return + allowed_configs += "[config]" + if(ispath(fake_atom, /obj/item)) + var/obj/item/item = fake_atom + if(initial(item.greyscale_config_worn)) + allowed_configs += "[initial(item.greyscale_config_worn)]" + if(initial(item.greyscale_config_inhand_left)) + allowed_configs += "[initial(item.greyscale_config_inhand_left)]" + if(initial(item.greyscale_config_inhand_right)) + allowed_configs += "[initial(item.greyscale_config_inhand_right)]" + + var/datum/greyscale_modify_menu/menu = new( + src, usr, allowed_configs, CALLBACK(src, .proc/vend_greyscale, params), + starting_icon_state=initial(fake_atom.icon_state), + starting_config=initial(fake_atom.greyscale_config), + starting_colors=initial(fake_atom.greyscale_colors) + ) + menu.ui_interact(usr) + +/obj/machinery/vending/proc/vend_greyscale(list/params, datum/greyscale_modify_menu/menu) + if(usr != menu.user) + return + if(!menu.target.can_interact(usr)) + return + vend(params, menu.split_colors) + +/obj/machinery/vending/proc/vend(list/params, list/greyscale_colors) + . = TRUE + if(!can_vend(usr)) + return + vend_ready = FALSE //One thing at a time!! + var/datum/data/vending_product/R = locate(params["ref"]) + var/list/record_to_check = product_records + coin_records + if(extended_inventory) + record_to_check = product_records + coin_records + hidden_records + if(!R || !istype(R) || !R.product_path) + vend_ready = TRUE + return + var/price_to_use = default_price + if(R.custom_price) + price_to_use = R.custom_price + if(R in hidden_records) + if(!extended_inventory) vend_ready = TRUE + return + else if (!(R in record_to_check)) + vend_ready = TRUE + message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") + return + if (R.amount <= 0) + say("Sold out of [R.name].") + flick(icon_deny,src) + vend_ready = TRUE + return + if(onstation) + var/obj/item/card/id/C + if(isliving(usr)) + var/mob/living/L = usr + C = L.get_idcard(TRUE) + if(!C) + say("No card found.") + flick(icon_deny,src) + vend_ready = TRUE + return + else if (!C.registered_account) + say("No account found.") + flick(icon_deny,src) + vend_ready = TRUE + return + else if(!C.registered_account.account_job) + say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.") + flick(icon_deny, src) + vend_ready = TRUE + return + else if(age_restrictions && R.age_restricted && (!C.registered_age || C.registered_age < AGE_MINOR)) + say("You are not of legal age to purchase [R.name].") + if(!(usr in GLOB.narcd_underages)) + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "SECURITY ALERT: Underaged crewmember [usr] recorded attempting to purchase [R.name] in [get_area(src)]. Please watch for substance abuse.", FREQ_SECURITY) + GLOB.narcd_underages += usr + flick(icon_deny,src) + vend_ready = TRUE + return + var/datum/bank_account/account = C.registered_account + if(account.account_job && account.account_job.paycheck_department == payment_department) + price_to_use = max(round(price_to_use * VENDING_DISCOUNT), 1) //No longer free, but signifigantly cheaper. + if(coin_records.Find(R) || hidden_records.Find(R)) + price_to_use = R.custom_premium_price ? R.custom_premium_price : extra_price + if(price_to_use && !account.adjust_money(-price_to_use)) + say("You do not possess the funds to purchase [R.name].") + flick(icon_deny,src) + vend_ready = TRUE + return + var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department) + if(D) + D.adjust_money(price_to_use) + SSblackbox.record_feedback("amount", "vending_spent", price_to_use) + log_econ("[price_to_use] credits were inserted into [src] by [D.account_holder] to buy [R].") + if(last_shopper != usr || purchase_message_cooldown < world.time) + say("Thank you for shopping with [src]!") + purchase_message_cooldown = world.time + 5 SECONDS + last_shopper = usr + use_power(5) + if(icon_vend) //Show the vending animation if needed + flick(icon_vend,src) + playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) + var/obj/item/vended_item = new R.product_path(get_turf(src)) + if(greyscale_colors) + vended_item.set_greyscale_colors(greyscale_colors) + R.amount-- + if(usr.CanReach(src) && usr.put_in_hands(vended_item)) + to_chat(usr, "You take [R.name] out of the slot.") + else + to_chat(usr, "[capitalize(R.name)] falls onto the floor!") + SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]")) + vend_ready = TRUE /obj/machinery/vending/process(delta_time) if(machine_stat & (BROKEN|NOPOWER)) diff --git a/icons/Testing/greyscale_error.dmi b/icons/Testing/greyscale_error.dmi new file mode 100644 index 0000000000000000000000000000000000000000..aacf6106821338c828806b53ec2ac53c6021e095 GIT binary patch literal 983 zcmV;|11S87P)*NAwI~(S(I9ynU?}KL0Q4o&jsub0Qear&!+$x zk^le#^+`lQR9J=8mO*aYFc3xMg?$4Ds88b~WKk6;*Kq*xG+Cqq+O;WwbIQ)2`I2qQ z4BY@N&?LY!7j4Bva05k4#!hw8nsyI=cD9SI{}&qY6e9j4q7g!YD3`;=(8t zB85mHQiv3ixBx;(9-PsHKp{{F6as}oTrjFIF#5rxKj2a%>K8`+!l-A01CrE0G6TvC zC}U}Gjyf^w#HbUaPT3xJ?I9_>9%_$Sd48bennl|#`)1=bTd&!??eR3~Zc%kq?Y~m?NL(0QjN;sMWX`DIlQCfHDKJ z;(VPS7UlpWPK-D~II)4653Bl^XEsZ&S+vcvZ#Pb}^_tDwZ2$E)UtHk(Lh>w(ti_RAj9g)yg-9V%h!i4)wDchJ;5DTA z8UlqtAyAle7}yVAYB_wPP|e}BCG45xmO*V-@2uKG3XR9zo%o_{mfXWdTP^!$<1|~Z z-Mr2AUmYlpFH4o;N~no#VVMJu3Z?kCP}^!flS<)K{0CMhdMT|>Z}BgL9~9ne!a(7L zLV24fZn5VU+j52fB8TQh)?TcCp(a1yI1dZe=KZk!e*?;# + icon_states: string[]; + finished: string; + steps: SpriteEntry[]; } type SpriteEntry = { - layer: string - result: string + layer: string; + result: string; } type GreyscaleMenuData = { greyscale_config: string; - colors: Array; + colors: ColorEntry[]; sprites: SpriteData; + generate_full_preview: boolean; + unlocked: boolean; sprites_dir: string; + icon_state: string; + refreshing: boolean; } enum Direction { @@ -49,9 +54,9 @@ const DirectionAbbreviation : Record = { const ConfigDisplay = (props, context) => { const { act, data } = useBackend(context); return ( -
+
- + - - - - - - - - -
+ + + + + + + + + + + + + + + + + + + + + ); }; @@ -149,23 +156,79 @@ const SingleDirection = (props, context) => { ); }; +const IconStatesDisplay = (props, context) => { + const { data, act } = useBackend(context); + return ( +
+ + { + data.sprites.icon_states.map(item => ( + +
+ ); +}; + const PreviewDisplay = (props, context) => { const { data } = useBackend(context); return (
- - - Step Layer - Step Result + + + + + { + data.sprites?.finished + ? ( + + + + ) + : ( + + + + + + ) + } - {data.sprites.steps.map(item => ( - - - - - ))}
+ { + !data.refreshing + && ( + + { + !!data.generate_full_preview && data.sprites.steps !== null + && ( + + Step Layer + Step Result + + ) + } + { + !!data.generate_full_preview && data.sprites.steps !== null + && data.sprites.steps.map(item => ( + + + + + )) + } +
+ ) + }
); }; @@ -183,26 +246,44 @@ const SingleSprite = (props) => { ); }; +const LoadingAnimation = () => { + return ( + + + + ); +}; + export const GreyscaleModifyMenu = (props, context) => { const { act, data } = useBackend(context); return ( -