Spritebloat final boss (#5704)

## About The Pull Request
SO! This pr does a few things:
One, it makes a new system for having properly dynamic uniforms. You can
select the core components (for most things, a shirt and pants),
accessories, etc. I've gone ahead and made a dynamic security uniform
via this. I also made a dynamic security cap!

Two, it adds a few black reskins. All the reskins do is remove some
small red accent. The reskins are available for the following:

Sec hud sunglasses
Sec gloves
Sec belt

Tackle gloves and gorrila gloves both can also now be reskinned to the
black variant that already existed.

Before anyone screams, I am adding NO blue. (Though, you could recolor
the uniform in the loadout)

(video was too big for github so here it is on catbox)
https://files.catbox.moe/8wtgkv.mp4
## Why It's Good For The Game
I kept feeling unsatisfied with my options as security, and as a whole.
Hopefully this is a better uniform system, even if we don't end up using
it that much.
## Proof Of Testing
<details>
<summary>Screenshots/Videos</summary>

</details>

## Changelog
🆑 UvvU
add: Added dynamic uniform support
add: Added black varients for the sec huds, belt, and gloves
/🆑
This commit is contained in:
CatoChristopherMrow
2026-06-23 22:06:44 +02:00
committed by GitHub
parent 4f87df6b81
commit 02e183642e
25 changed files with 1444 additions and 22 deletions
@@ -31,6 +31,8 @@
/// Whether the menu is in the middle of refreshing the preview
var/refreshing = TRUE
///BUBBER VAR: Optional display labels for color groups, keyed by color index.
var/list/color_labels
/**
* Whether the menu is currently locked down to prevent abuse from players.
@@ -102,12 +104,14 @@
/datum/greyscale_modify_menu/ui_data(mob/user)
var/list/data = list()
data["greyscale_config"] = "[config.name]"
data["full_color_string"] = split_colors?.Join("") //BUBBER ADDITION
var/list/color_data = list()
data["colors"] = color_data
for(var/i in 1 to config.expected_colors)
color_data += list(list(
"index" = i,
"label" = color_labels?["[i]"] || color_labels?[i], //BUBBER ADDITION
"value" = split_colors[i]
))
+5 -1
View File
@@ -407,7 +407,11 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
formatted_item["reskins"] = get_reskin_options()
formatted_item["icon"] = ui_icon
formatted_item["icon_state"] = ui_icon_state
formatted_item["ckey_whitelist"] = ckeywhitelist // BUBBER EDIT ADDITION: Filter ckey-locked items
//BUBBER EDIT ADDITION START - Dynamic Uniforms + ckey lock filter
if(initial(item_path.greyscale_component_style_type))
add_component_style_preview_to_ui_data(formatted_item)
formatted_item["ckey_whitelist"] = ckeywhitelist
//BUBBER EDIT ADDITION END
return formatted_item
+5
View File
@@ -204,6 +204,11 @@
if(greyscale_colors)
vended_item.set_greyscale(colors=greyscale_colors)
//BUBBER ADDITION START - dynamic uniforms
var/list/component_style_details = greyscale_component_style_vend_details?[REF(user)]
if(component_style_details && vended_item.greyscale_component_style_type)
vended_item.apply_greyscale_component_style(component_style_details)
//BUBBER ADDITION END
if(IsReachableBy(user) && user.put_in_hands(vended_item))
to_chat(user, span_notice("You take [item_record.name] out of the slot."))
vended_item.do_pickup_animation(user, src)
+18 -1
View File
@@ -44,11 +44,22 @@
)
var/atom/printed = record.product_path
//BUBBER ADDITION START - Dynamic uniforms
var/component_preview_applied = FALSE
if(ispath(printed, /obj/item))
var/obj/item/printed_item = printed
if(initial(printed_item.greyscale_component_style_type))
static_record["image"] = get_component_style_vending_preview_image(printed_item)
component_preview_applied = TRUE
//BUBBER ADDITION END
// If it's not GAGS and has no innate colors we have to care about, we use DMIcon
if(ispath(printed, /atom) \
//BUBBER EDIT START - dynamic uniform
if(!component_preview_applied \
&& ispath(printed, /atom) \
&& (!initial(printed.greyscale_config) || !initial(printed.greyscale_colors)) \
&& !initial(printed.color) \
)
//BUBBER EDIT END
static_record["icon"] = initial(printed.icon)
static_record["icon_state"] = initial(printed.icon_state)
@@ -147,6 +158,12 @@
if(!istype(product))
return FALSE
var/atom/fake_atom = product.product_path
//BUBBER ADDITION START - Dynamic uniforms
if(ispath(fake_atom, /obj/item))
var/obj/item/fake_item = fake_atom
if(initial(fake_item.greyscale_component_style_type))
return open_component_style_vending_menu(product, params, ui.user)
//BUBBER ADDITION END
var/config = initial(fake_atom.greyscale_config)
if(!config)
return FALSE
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 26 KiB

+13
View File
@@ -0,0 +1,13 @@
/// Tracks selected core components for component-style GAGS loadout items.
#define INFO_GREYSCALE_COMPONENT_CORES "greyscale_component_cores"
/// Tracks selected optional components for component-style GAGS loadout items.
#define INFO_GREYSCALE_COMPONENT_ACCESSORIES "greyscale_component_accessories"
/// Accessory applies to every option in a core component slot.
#define COMPONENT_CORE_ALL "all"
/// Accessory applies only to the named core option.
#define COMPONENT_CORE_EXACT "exact"
/// Accessory applies to the named core option and any option descended from it.
#define COMPONENT_CORE_BRANCH "branch"
/// Accessory applies to a named group defined on the core component.
#define COMPONENT_CORE_GROUP "group"
@@ -8,6 +8,47 @@
icon_file = 'modular_zubbers/icons/mob/clothing/under/captain.dmi'
json_config = 'modular_zubbers/code/datums/greyscale/json_configs/cso.json'
/datum/greyscale_config/security_uniform
name = "Security Uniform"
icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
greyscale_component_style_type = /datum/greyscale_component_style/security_uniform
greyscale_component_config_states = list("recolorable_security_uniform", "rsecurity")
/datum/greyscale_config/security_uniform/New()
InitializeGreyscaleComponentStyleConfig()
/datum/greyscale_config/security_uniform/Refresh(loadFromDisk = FALSE)
return RefreshGreyscaleComponentStyleConfig(loadFromDisk)
/datum/greyscale_config/security_uniform/worn
name = "Security Uniform (Worn)"
icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
/datum/greyscale_config/security_uniform/worn/digi
name = "Security Uniform (Worn, Digi)"
icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
/datum/greyscale_config/security_cap
name = "Security Cap"
icon_file = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
greyscale_component_style_type = /datum/greyscale_component_style/security_cap
greyscale_component_config_states = list("security_cap")
/datum/greyscale_config/security_cap/New()
InitializeGreyscaleComponentStyleConfig()
/datum/greyscale_config/security_cap/Refresh(loadFromDisk = FALSE)
return RefreshGreyscaleComponentStyleConfig(loadFromDisk)
/datum/greyscale_config/security_cap/worn
name = "Security Cap (Worn)"
icon_file = 'modular_zubbers/icons/mob/clothing/head/hats.dmi'
greyscale_component_config_state_overrides = list(
"security_cap" = "security_capg",
"security_cap_accs_front" = "security_capg_accs_backing",
"security_cap_accs_logo" = "security_capg_accs_logo",
)
/datum/greyscale_config/gi/worn/digi
name = "Gi (Worn, Digi)"
icon_file = 'modular_skyrat/master_files/icons/mob/clothing/under/costume_digi.dmi'
@@ -46,6 +46,75 @@
icon_state = "secsoft_v"
armor_type = /datum/armor/cosmetic_sec
/datum/greyscale_component_style/security_cap
output_icon_state = "security_cap"
fallback_icon_state = "security_cap"
default_accessories = list("security_cap_accs_front", "security_cap_accs_logo")
core_components = list(
list(
"name" = "Cap",
"key" = "cap",
"default" = "security",
"options" = list(
"security" = list(
"name" = "Security cap",
"state" = "security_cap",
"color_id" = 1,
"color_label" = "Main",
"default_color" = "#A53429",
),
),
),
)
accessories = list(
"Backing" = list(
"state" = "security_cap_accs_front",
"color_id" = 2,
"color_label" = "Backing",
"default_color" = "#3F6E9E",
),
"Logo" = list(
"state" = "security_cap_accs_logo",
"color_id" = 3,
"color_label" = "Logo",
"default_color" = "#FFFFFF",
),
)
/obj/item/clothing/head/soft/sec/recolorable
name = "security cap"
desc = "It's a robust baseball hat, this one seems to have been custom ordered."
icon = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
worn_icon = 'modular_zubbers/icons/mob/clothing/head/hats.dmi'
icon_state = "security_cap"
post_init_icon_state = "security_cap"
soft_type = "security_cap"
soft_suffix = ""
greyscale_config = /datum/greyscale_config/security_cap
greyscale_config_worn = /datum/greyscale_config/security_cap/worn
greyscale_colors = "#A53429#3F6E9E#FFFFFF"
flags_1 = parent_type::flags_1 | IS_PLAYER_COLORABLE_1
greyscale_component_style_type = /datum/greyscale_component_style/security_cap
greyscale_component_icon_file = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
greyscale_component_worn_icon_file = 'modular_zubbers/icons/mob/clothing/head/hats.dmi'
greyscale_component_worn_state_overrides = list(
"security_cap" = "security_capg",
"security_cap_accs_front" = "security_capg_accs_backing",
"security_cap_accs_logo" = "security_capg_accs_logo",
)
greyscale_component_accessories = list("security_cap_accs_front", "security_cap_accs_logo")
/obj/item/clothing/head/soft/sec/recolorable/Initialize(mapload)
initialize_greyscale_component_style()
return ..()
/obj/item/clothing/head/soft/sec/recolorable/update_greyscale()
. = ..()
update_greyscale_component_icons()
/obj/item/clothing/head/soft/sec/recolorable/flip(mob/user)
balloon_alert(user, "can't be flipped!")
/obj/item/clothing/head/sec/viro/beanie
name = "security beanie"
desc = "A beanie for security purposes"
@@ -92,6 +92,196 @@
can_adjust = TRUE
supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION
/datum/greyscale_component_style/security_uniform
output_icon_state = "rsecurity"
fallback_icon_state = "security_shirt"
default_accessories = list("security_shirt_accs_tie")
core_components = list(
list(
"name" = "Shirt",
"key" = "shirt",
"default" = "security",
"groups" = list(
"tie_compatible" = list("security", "viro", "suit"),
"arm_trim_compatible" = list("security", "viro", "turtleneck"),
),
"options" = list(
"security" = list(
"name" = "Security shirt",
"state" = "security_shirt",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
"viro" = list(
"name" = "Virosec shirt",
"state" = "security_shirt_viro",
"parent" = "security",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
"suit" = list(
"name" = "Security suit",
"state" = "security_shirt_suit",
"parent" = "security",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
"turtleneck" = list(
"name" = "Security turtleneck",
"state" = "security_shirt_turtleneck",
"parent" = "security",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
),
),
list(
"name" = "Pants",
"key" = "pants",
"default" = "security",
"options" = list(
"security" = list(
"name" = "Security pants",
"state" = "security_pants",
"color_id" = 2,
"color_label" = "Pants",
"default_color" = "#4B4C51",
),
"skirt" = list(
"name" = "Security skirt",
"state" = "security_pants_skirt",
"parent" = "security",
"color_id" = 2,
"color_label" = "Pants",
"default_color" = "#4B4C51",
),
"viro" = list(
"name" = "Virosec pants",
"state" = "security_pants_viro",
"parent" = "security",
"color_id" = 2,
"color_label" = "Pants",
"default_color" = "#4B4C51",
),
),
),
)
accessories = list(
"Tie" = list(
"state" = "security_shirt_accs_tie",
"applies_to" = list("shirt" = list(COMPONENT_CORE_GROUP = "tie_compatible")),
"color_id" = 3,
"color_label" = "Tie",
"default_color" = "#2D2D33",
),
"Arm trim one" = list(
"state" = "security_shirt_accs_arm_trim_one",
"applies_to" = list("shirt" = list(COMPONENT_CORE_EXACT = list("security", "viro"))),
"color_id" = 4,
"color_label" = "Arm trim one",
"default_color" = "#39393F",
),
"Arm trim two" = list(
"state" = "security_shirt_accs_arm_trim_two",
"applies_to" = list("shirt" = list(COMPONENT_CORE_GROUP = "arm_trim_compatible")),
"color_id" = 13,
"color_label" = "Arm trim two",
"default_color" = "#46464D",
),
"Arm trim three" = list(
"state" = "security_shirt_accs_arm_trim_three",
"applies_to" = list("shirt" = list(COMPONENT_CORE_GROUP = "arm_trim_compatible")),
"color_id" = 12,
"color_label" = "Arm trim three",
"default_color" = "#D0D0D0",
),
"Epaulettes" = list(
"state" = "security_shirt_accss_epaulettes",
"applies_to" = list("shirt" = list(COMPONENT_CORE_BRANCH = "security")),
"color_id" = 11,
"color_label" = "Epaulettes",
"default_color" = "#39393F",
),
"Viro shirt trim" = list(
"state" = "security_shirt_accs_trim_viro",
"applies_to" = list("shirt" = list("viro" = COMPONENT_CORE_EXACT)),
"color_id" = 8,
"color_label" = "Viro shirt trim",
"default_color" = "#46464D",
),
"Suit inner shirt" = list(
"state" = "security_shirt_suit_accs_inner",
"applies_to" = list("shirt" = list("suit" = COMPONENT_CORE_EXACT)),
"color_id" = 9,
"color_label" = "Suit inner shirt",
"default_color" = "#EBEBEB",
),
"Buckle" = list(
"state" = "security_pants_accs_buckle",
"applies_to" = list("pants" = COMPONENT_CORE_ALL),
"color_id" = 10,
"color_label" = "Buckle",
"default_color" = "#B5BBCF",
),
"Skirt outer trim" = list(
"state" = "security_pants_skirt_accs_outer_trim",
"applies_to" = list("pants" = list("skirt" = COMPONENT_CORE_EXACT)),
"color_id" = 5,
"color_label" = "Skirt outer trim",
"default_color" = "#46464D",
),
"Skirt inner trim" = list(
"state" = "security_pants_skirt_accs_inner_trim",
"applies_to" = list("pants" = list("skirt" = COMPONENT_CORE_EXACT)),
"color_id" = 6,
"color_label" = "Skirt inner trim",
"default_color" = "#46464D",
),
"Viro pants trim" = list(
"state" = "security_pants_viro_accs_trim",
"applies_to" = list("pants" = list("viro" = COMPONENT_CORE_EXACT)),
"color_id" = 7,
"color_label" = "Viro pants trim",
"default_color" = "#7D2A25",
),
)
/obj/item/clothing/under/rank/security/officer/recolorable
name = "security uniform"
desc = "A tactical security jumpsuit for officers. This one seems to have a few custom modifications."
icon = 'icons/map_icons/clothing/under/_under.dmi'
worn_icon = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
worn_icon_digi = 'modular_zubbers/icons/mob/clothing/under/security_digi.dmi'
icon_state = "/obj/item/clothing/under/rank/security/officer/recolorable"
post_init_icon_state = "recolorable_security_uniform"
greyscale_config = /datum/greyscale_config/security_uniform
greyscale_config_worn = /datum/greyscale_config/security_uniform/worn
greyscale_config_worn_digi = /datum/greyscale_config/security_uniform/worn/digi
greyscale_colors = "#BA3B2E#4B4C51#2D2D33#39393F#46464D#46464D#7D2A25#46464D#EBEBEB#B5BBCF#39393F#D0D0D0#46464D"
flags_1 = parent_type::flags_1 | IS_PLAYER_COLORABLE_1
can_adjust = TRUE
supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION
greyscale_component_style_type = /datum/greyscale_component_style/security_uniform
greyscale_component_worn_icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
greyscale_component_worn_digi_icon_file = 'modular_zubbers/icons/mob/clothing/under/security_digi.dmi'
greyscale_component_digi_fallback_icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
greyscale_component_accessories = list("security_shirt_accs_tie")
/obj/item/clothing/under/rank/security/officer/recolorable/Initialize(mapload)
initialize_greyscale_component_style()
return ..()
/obj/item/clothing/under/rank/security/officer/recolorable/update_greyscale()
. = ..()
update_greyscale_component_icons()
icon = 'icons/obj/clothing/under/security.dmi'
icon_state = "rsecurity"
inhand_icon_state = "r_suit"
// Virosec, sprites by axietheaxolotl
/obj/item/clothing/under/rank/security/viro/officer/
@@ -0,0 +1,866 @@
/datum/greyscale_component_style
/// Marker used when loadout storage needs to preserve an intentionally empty accessory selection.
var/static/no_accessories_marker = "__component_style_no_accessories"
/datum/greyscale_component_style
/// Icon state inserted into generated icon files.
var/output_icon_state
/// Fallback component state to use if every selected component is missing.
var/fallback_icon_state
/// Suffix checked first for digitigrade component states.
var/digi_suffix = "_digi"
/// Ordered list of core component definitions.
var/list/core_components = list()
/// Optional component definitions, keyed by display name.
var/list/accessories = list()
/// Optional accessory component states enabled by default.
var/list/default_accessories = list()
/// Suffix checked first for adjusted/casual component states.
var/adjusted_suffix = "_d"
/datum/greyscale_component_style/proc/default_core_selection() as /list
var/list/selection = list()
for(var/list/core_component as anything in core_components)
selection[core_component["key"]] = core_component["default"]
return selection
/datum/greyscale_component_style/proc/sanitize_core_selection(list/selected_cores) as /list
var/list/sanitized = list()
for(var/list/core_component as anything in core_components)
var/core_key = core_component["key"]
var/selected_state = selected_cores?[core_key] || core_component["default"]
if(!core_option_exists(core_component, selected_state))
selected_state = core_component["default"]
sanitized[core_key] = selected_state
return sanitized
/datum/greyscale_component_style/proc/sanitize_accessories(list/selected_accessories, list/selected_cores) as /list
var/list/sanitized = list()
if(!islist(selected_accessories) || (no_accessories_marker in selected_accessories))
return sanitized
for(var/accessory in selected_accessories)
if(!accessory_allowed(accessory, selected_cores))
continue
sanitized += accessory
return sanitized
/datum/greyscale_component_style/proc/core_option_exists(list/core_component, option_state)
var/list/options = core_component["options"]
return islist(options?[option_state])
/datum/greyscale_component_style/proc/core_component_for_key(core_key) as /list
for(var/list/core_component as anything in core_components)
if(core_component["key"] == core_key)
return core_component
return null
/datum/greyscale_component_style/proc/core_option_data(list/core_component, option_id) as /list
var/list/options = core_component?["options"]
return options?[option_id]
/datum/greyscale_component_style/proc/core_option_state(list/core_component, option_id)
var/list/option_data = core_option_data(core_component, option_id)
return option_data?["state"]
/datum/greyscale_component_style/proc/core_group_options(list/core_component, group_id) as /list
var/list/groups = core_component?["groups"]
var/list/group_options = groups?[group_id]
return islist(group_options) ? group_options : null
/datum/greyscale_component_style/proc/core_option_matches(list/core_component, selected_option, target_option, match_mode)
if(match_mode == COMPONENT_CORE_ALL)
return TRUE
if(match_mode == COMPONENT_CORE_EXACT)
return selected_option == target_option
if(match_mode != COMPONENT_CORE_BRANCH)
return FALSE
var/current_option = selected_option
while(current_option)
if(current_option == target_option)
return TRUE
var/list/option_data = core_option_data(core_component, current_option)
current_option = option_data?["parent"]
return FALSE
/datum/greyscale_component_style/proc/core_rule_targets(list/core_component, rule_value, use_groups = FALSE) as /list
if(islist(rule_value))
return rule_value
if(use_groups)
return core_group_options(core_component, rule_value) || list()
return list(rule_value)
/datum/greyscale_component_style/proc/core_apply_rule_matches(list/core_component, selected_option, rule_mode, rule_value)
var/list/targets = core_rule_targets(core_component, rule_value, rule_mode == COMPONENT_CORE_GROUP)
var/match_mode = (rule_mode == COMPONENT_CORE_GROUP) ? COMPONENT_CORE_EXACT : rule_mode
for(var/target_option in targets)
if(core_option_matches(core_component, selected_option, target_option, match_mode))
return TRUE
return FALSE
/datum/greyscale_component_style/proc/core_ui_data(list/selected_cores) as /list
var/list/core_data = list()
for(var/list/core_component as anything in core_components)
var/core_key = core_component["key"]
core_data += list(list(
"name" = core_component["name"],
"key" = core_key,
"options" = option_ui_data(core_component["options"], selected_cores[core_key]),
))
return core_data
/datum/greyscale_component_style/proc/option_ui_data(list/options, selected_value) as /list
var/list/style_options = list()
for(var/option_id in options)
var/list/option_data = options[option_id]
style_options += list(list(
"name" = option_data["name"] || option_id,
"value" = option_id,
"selected" = (option_id == selected_value),
))
return style_options
/datum/greyscale_component_style/proc/accessory_ui_data(list/selected_accessories, list/selected_cores) as /list
var/list/style_options = list()
for(var/accessory_name in accessories)
var/list/accessory_rules = accessories[accessory_name]
var/accessory_state = accessory_rules["state"]
if(!accessory_allowed(accessory_state, selected_cores))
continue
style_options += list(list(
"name" = accessory_name,
"value" = accessory_state,
"selected" = (accessory_state in selected_accessories),
))
return style_options
/datum/greyscale_component_style/proc/accessory_allowed(accessory_state, list/selected_cores)
var/list/accessory_rules = accessory_rules_for_state(accessory_state)
if(isnull(accessory_rules))
return FALSE
var/list/apply_rules = accessory_rules["applies_to"]
if(!length(apply_rules))
return TRUE
for(var/core_key in apply_rules)
var/list/core_component = core_component_for_key(core_key)
if(!core_component)
return FALSE
var/selected_option = selected_cores?[core_key]
if(!selected_option)
return FALSE
var/apply_rule = apply_rules[core_key]
if(apply_rule == COMPONENT_CORE_ALL)
continue
var/matched_core = FALSE
if(islist(apply_rule))
for(var/rule_key in apply_rule)
if(rule_key in list(COMPONENT_CORE_EXACT, COMPONENT_CORE_BRANCH, COMPONENT_CORE_GROUP))
if(core_apply_rule_matches(core_component, selected_option, rule_key, apply_rule[rule_key]))
matched_core = TRUE
break
continue
if(core_apply_rule_matches(core_component, selected_option, apply_rule[rule_key] || COMPONENT_CORE_EXACT, rule_key))
matched_core = TRUE
break
else
matched_core = core_option_matches(core_component, selected_option, apply_rule, COMPONENT_CORE_EXACT)
if(!matched_core)
return FALSE
return TRUE
/datum/greyscale_component_style/proc/accessory_rules_for_state(accessory_state) as /list
for(var/accessory_name in accessories)
var/list/accessory_rules = accessories[accessory_name]
if(accessory_rules["state"] == accessory_state)
return accessory_rules
return null
/datum/greyscale_component_style/proc/active_components(list/selected_cores, list/selected_accessories) as /list
var/list/components = list()
for(var/list/core_component as anything in core_components)
var/component_state = core_option_state(core_component, selected_cores?[core_component["key"]])
if(component_state)
components += component_state
if(islist(selected_accessories))
for(var/accessory_state in selected_accessories)
if(!accessory_state)
continue
components += accessory_state
return components
/datum/greyscale_component_style/proc/color_id_for_component(component_state)
var/list/component_rules = component_rules_for_state(component_state)
return component_rules?["color_id"]
/datum/greyscale_component_style/proc/component_rules_for_state(component_state) as /list
for(var/list/core_component as anything in core_components)
var/list/options = core_component["options"]
for(var/option_id in options)
var/list/option_data = options[option_id]
if(option_data["state"] == component_state)
return option_data
for(var/accessory_name in accessories)
var/list/accessory_rules = accessories[accessory_name]
if(accessory_rules["state"] == component_state)
return accessory_rules
return null
/datum/greyscale_component_style/proc/active_color_ids(list/selected_cores, list/selected_accessories) as /list
var/list/active_color_ids = list()
for(var/component in active_components(selected_cores, selected_accessories))
var/color_id = color_id_for_component(component)
if(!color_id || (color_id in active_color_ids))
continue
active_color_ids += color_id
return active_color_ids
/datum/greyscale_component_style/proc/active_color_labels(list/selected_cores, list/selected_accessories) as /list
var/list/labels = list()
for(var/component_state in active_components(selected_cores, selected_accessories))
var/list/component_rules = component_rules_for_state(component_state)
var/color_id = component_rules?["color_id"]
var/color_label = component_rules?["color_label"]
if(!color_id || !color_label)
continue
labels["[color_id]"] = color_label
return labels
/datum/greyscale_component_style/proc/default_color_list(list/fallback_colors, expected_colors = 0) as /list
var/list/colors = islist(fallback_colors) ? fallback_colors.Copy() : list()
while(length(colors) < expected_colors)
colors += null
for(var/list/core_component as anything in core_components)
var/list/options = core_component["options"]
for(var/option_id in options)
apply_component_default_color(colors, options[option_id])
for(var/accessory_name in accessories)
apply_component_default_color(colors, accessories[accessory_name])
for(var/i in 1 to length(colors))
if(!colors[i])
colors[i] = rgb(100, 100, 100)
return colors
/datum/greyscale_component_style/proc/apply_component_default_color(list/colors, list/component_rules)
var/color_id = component_rules?["color_id"]
var/default_color = component_rules?["default_color"]
if(!color_id || !default_color)
return
while(length(colors) < color_id)
colors += null
colors[color_id] = default_color
/datum/greyscale_component_style/proc/greyscale_config_data(list/output_states, list/state_overrides) as /list
var/list/layers = greyscale_config_layers(state_overrides)
var/list/config_data = list()
if(!length(output_states))
output_states = list(output_icon_state)
for(var/output_state in output_states)
if(isnull(output_state))
continue
config_data[output_state] = layers
return config_data
/datum/greyscale_component_style/proc/greyscale_config_layers(list/state_overrides) as /list
var/list/layers = list()
var/list/seen_states = list()
for(var/component_state in greyscale_config_component_states())
if(!component_state || seen_states[component_state])
continue
seen_states[component_state] = TRUE
var/list/component_rules = component_rules_for_state(component_state)
var/layer_state = islist(state_overrides) ? (state_overrides[component_state] || component_state) : component_state
var/list/layer_data = list(
"type" = "icon_state",
"icon_state" = layer_state,
"blend_mode" = "overlay",
)
var/color_id = component_rules?["color_id"]
if(color_id)
layer_data["color_ids"] = list(color_id)
layers += list(layer_data)
return layers
/datum/greyscale_component_style/proc/greyscale_config_component_states() as /list
var/list/component_states = list()
for(var/list/core_component as anything in core_components)
var/list/options = core_component["options"]
for(var/option_id in options)
component_states += options[option_id]["state"]
for(var/accessory_name in accessories)
component_states += accessories[accessory_name]["state"]
return component_states
/datum/greyscale_config
/// Optional dynamic component-style definition used to generate this config without a hand-written JSON file.
var/greyscale_component_style_type
/// Icon states this generated config should expose in its output bundle.
var/list/greyscale_component_config_states
/// Optional logical component state to real icon state mapping for this config's icon file.
var/list/greyscale_component_config_state_overrides
/datum/greyscale_config/proc/InitializeGreyscaleComponentStyleConfig()
if(!icon_file)
stack_trace("Greyscale component-style config object [DebugName()] is missing an icon file, make sure `icon_file` has been assigned a value.")
string_icon_file = "[icon_file]"
if(!name)
stack_trace("Greyscale component-style config object [DebugName()] is missing a name, make sure `name` has been assigned a value.")
if(!greyscale_component_style_type)
stack_trace("Greyscale component-style config object [DebugName()] is missing a component style type.")
/datum/greyscale_config/proc/RefreshGreyscaleComponentStyleConfig(loadFromDisk = FALSE)
if(loadFromDisk)
var/changed = FALSE
icon_file = file(string_icon_file)
var/icon_hash = rustg_hash_file("md5", string_icon_file)
if(icon_file_hash != icon_hash)
icon_file_hash = icon_hash
changed = TRUE
for(var/datum/greyscale_layer/layer as anything in flat_all_layers)
if(layer.DiskRefresh())
changed = TRUE
if(!changed)
return FALSE
var/style_type = greyscale_component_style_type
var/datum/greyscale_component_style/component_style = new style_type
var/list/raw = component_style.greyscale_config_data(greyscale_component_config_states, greyscale_component_config_state_overrides)
raw_json_string = json_encode(raw)
qdel(component_style)
ReadIconStateConfiguration(raw)
if(!length(icon_states))
CRASH("The component-style greyscale configuration [DebugName()] doesn't have any icon states.")
icon_cache = list()
ReadMetadata()
SEND_SIGNAL(src, COMSIG_GREYSCALE_CONFIG_REFRESHED)
return TRUE
/datum/greyscale_component_style/proc/build_icon(icon_file, list/colors, list/selected_cores, list/selected_accessories, use_digi = FALSE, digi_fallback_icon_file = null, only_dir = null, list/state_overrides = null, use_adjusted = FALSE)
var/icon/final_icon = icon('icons/effects/effects.dmi', "nothing")
final_icon.Scale(32, 32)
if(!icon_file)
return final_icon
var/icon/built_icon
for(var/component in active_components(selected_cores, selected_accessories))
if(!component)
continue
var/layer_file = icon_file
var/state_to_use = component
if(islist(state_overrides))
state_to_use = state_overrides[component] || component
var/list/candidate_states = list()
if(use_digi && use_adjusted)
candidate_states += "[state_to_use][digi_suffix][adjusted_suffix]"
if(use_adjusted)
candidate_states += "[state_to_use][adjusted_suffix]"
if(use_digi)
candidate_states += "[state_to_use][digi_suffix]"
candidate_states += state_to_use
var/list/candidate_files = list(layer_file)
if(use_digi && digi_fallback_icon_file)
candidate_files += digi_fallback_icon_file
var/found_component_state = FALSE
for(var/candidate_state in candidate_states)
for(var/candidate_file in candidate_files)
if(!icon_exists(candidate_file, candidate_state))
continue
layer_file = candidate_file
state_to_use = candidate_state
found_component_state = TRUE
break
if(found_component_state)
break
if(!found_component_state)
continue
var/icon/component_icon = isnull(only_dir) ? icon(layer_file, state_to_use) : icon(layer_file, state_to_use, only_dir)
var/color_id = color_id_for_component(component)
if(color_id && length(colors) >= color_id && colors[color_id])
component_icon.Blend(colors[color_id], ICON_MULTIPLY)
if(isnull(built_icon))
built_icon = component_icon
else
built_icon.Blend(component_icon, ICON_OVERLAY)
if(isnull(built_icon) && fallback_icon_state)
built_icon = isnull(only_dir) ? icon(icon_file, fallback_icon_state) : icon(icon_file, fallback_icon_state, only_dir)
if(!isnull(built_icon))
var/generated_icon_state = use_adjusted ? "[output_icon_state][adjusted_suffix]" : output_icon_state
final_icon.Insert(built_icon, generated_icon_state)
return final_icon
/obj/item
/// Optional component-style GAGS definition used for layered, selectable components.
var/greyscale_component_style_type
/// Selected core option ids, keyed by core component id.
var/list/greyscale_component_cores
/// Selected optional component states.
var/list/greyscale_component_accessories
/// Object icon file used for component-style GAGS composition.
var/greyscale_component_icon_file
/// Worn icon file used for component-style GAGS composition.
var/greyscale_component_worn_icon_file
/// Digitigrade worn icon file used for component-style GAGS composition.
var/greyscale_component_worn_digi_icon_file
/// Optional non-digi worn icon fallback for missing digitigrade component states.
var/greyscale_component_digi_fallback_icon_file
/// Whether component-style worn icons should use adjusted/casual component states.
var/greyscale_component_use_adjusted = FALSE
/// Optional logical component state to worn icon state mapping.
var/list/greyscale_component_worn_state_overrides
/// Optional logical component state to digitigrade worn icon state mapping.
var/list/greyscale_component_worn_digi_state_overrides
/obj/item/proc/get_greyscale_component_style() as /datum/greyscale_component_style
if(!greyscale_component_style_type)
return null
return new greyscale_component_style_type
/obj/item/proc/initialize_greyscale_component_style()
var/datum/greyscale_component_style/component_style = get_greyscale_component_style()
if(!component_style)
return
greyscale_component_cores = component_style.sanitize_core_selection(greyscale_component_cores)
var/list/accessories_to_apply = isnull(greyscale_component_accessories) ? component_style.default_accessories : greyscale_component_accessories
greyscale_component_accessories = component_style.sanitize_accessories(accessories_to_apply, greyscale_component_cores)
qdel(component_style)
/obj/item/proc/apply_greyscale_component_style(list/item_details)
var/datum/greyscale_component_style/component_style = get_greyscale_component_style()
if(!component_style)
return
greyscale_component_cores = component_style.sanitize_core_selection(item_details?[INFO_GREYSCALE_COMPONENT_CORES] || greyscale_component_cores)
var/list/accessories_to_apply = item_details?[INFO_GREYSCALE_COMPONENT_ACCESSORIES]
if(isnull(accessories_to_apply))
accessories_to_apply = isnull(greyscale_component_accessories) ? component_style.default_accessories : greyscale_component_accessories
greyscale_component_accessories = component_style.sanitize_accessories(accessories_to_apply, greyscale_component_cores)
qdel(component_style)
update_greyscale()
/obj/item/proc/update_greyscale_component_icons()
var/datum/greyscale_component_style/component_style = get_greyscale_component_style()
if(!component_style || !greyscale_colors)
qdel(component_style)
return
var/list/colors = SSgreyscale.ParseColorString(greyscale_colors)
icon_state = component_style.output_icon_state
worn_icon_state = greyscale_component_use_adjusted ? "[component_style.output_icon_state][component_style.adjusted_suffix]" : component_style.output_icon_state
greyscale_config_worn = null
greyscale_config_worn_digi = null
if(greyscale_component_icon_file)
icon = component_style.build_icon(greyscale_component_icon_file, colors, greyscale_component_cores, greyscale_component_accessories)
if(greyscale_component_worn_icon_file)
worn_icon = component_style.build_icon(greyscale_component_worn_icon_file, colors, greyscale_component_cores, greyscale_component_accessories, state_overrides = greyscale_component_worn_state_overrides, use_adjusted = greyscale_component_use_adjusted)
if(greyscale_component_worn_digi_icon_file)
worn_icon_digi = component_style.build_icon(greyscale_component_worn_digi_icon_file, colors, greyscale_component_cores, greyscale_component_accessories, use_digi = TRUE, digi_fallback_icon_file = greyscale_component_digi_fallback_icon_file, state_overrides = greyscale_component_worn_digi_state_overrides || greyscale_component_worn_state_overrides, use_adjusted = greyscale_component_use_adjusted)
qdel(component_style)
var/obj/item/clothing/worn_clothing = src
if(!istype(worn_clothing))
return
var/mob/living/carbon/human/wearer = worn_clothing.loc
if(!istype(wearer))
return
wearer.update_clothing(worn_clothing.slot_flags)
/obj/item/clothing/under/adjust_to_normal()
. = ..()
if(!greyscale_component_style_type)
return
greyscale_component_use_adjusted = FALSE
update_greyscale_component_icons()
/obj/item/clothing/under/adjust_to_alt()
. = ..()
if(!greyscale_component_style_type)
return
greyscale_component_use_adjusted = TRUE
update_greyscale_component_icons()
/datum/loadout_item/proc/uses_greyscale_component_style()
return !!initial(item_path.greyscale_component_style_type)
/datum/loadout_item
/// Optional icon file used for component-style color previews instead of the item's worn preview.
var/greyscale_component_preview_icon_file
/// Optional logical component state to preview icon state mapping.
var/list/greyscale_component_preview_state_overrides
/// Opens a component-style GAGS editing menu when the loadout item supports it.
/datum/loadout_item/proc/set_component_style_item_color(datum/preference_middleware/loadout/manager, mob/user)
if(manager.menu)
return FALSE
var/list/loadout = manager.get_current_loadout()
var/list/allowed_configs = list()
if(initial(item_path.greyscale_config_worn))
allowed_configs += "[initial(item_path.greyscale_config_worn)]"
else if(initial(item_path.greyscale_config))
allowed_configs += "[initial(item_path.greyscale_config)]"
var/list/selected_cores = loadout?[item_path]?[INFO_GREYSCALE_COMPONENT_CORES]
if(isnull(selected_cores))
selected_cores = initial(item_path.greyscale_component_cores)
var/list/selected_accessories = loadout?[item_path]?[INFO_GREYSCALE_COMPONENT_ACCESSORIES]
if(islist(selected_accessories) && (/datum/greyscale_component_style::no_accessories_marker in selected_accessories))
selected_accessories = list()
var/starting_config = initial(item_path.greyscale_config_worn) || initial(item_path.greyscale_config)
var/starting_colors = loadout?[item_path]?[INFO_GREYSCALE] || initial(item_path.greyscale_colors)
var/datum/greyscale_config/selected_config = SSgreyscale.configurations["[starting_config]"]
if(selected_config && length(SSgreyscale.ParseColorString(starting_colors)) != selected_config.expected_colors)
starting_colors = initial(item_path.greyscale_colors)
var/preview_icon_file = greyscale_component_preview_icon_file || initial(item_path.greyscale_component_worn_icon_file) || initial(item_path.greyscale_component_icon_file)
var/list/preview_state_overrides = greyscale_component_preview_state_overrides
if(!preview_state_overrides && preview_icon_file == initial(item_path.greyscale_component_worn_icon_file))
preview_state_overrides = initial(item_path.greyscale_component_worn_state_overrides)
var/datum/greyscale_modify_menu/component_style/menu = new(
manager,
user,
allowed_configs,
CALLBACK(src, PROC_REF(set_slot_greyscale), manager),
starting_icon_state = initial(item_path.icon_state),
starting_config = starting_config,
starting_colors = starting_colors,
component_style_type = initial(item_path.greyscale_component_style_type),
selected_cores = selected_cores,
selected_accessories = selected_accessories,
preview_icon_file = preview_icon_file,
preview_digi_fallback_icon_file = initial(item_path.greyscale_component_digi_fallback_icon_file),
preview_state_overrides = preview_state_overrides,
default_colors = initial(item_path.greyscale_colors),
default_cores = initial(item_path.greyscale_component_cores),
default_accessories = null,
)
manager.register_greyscale_menu(menu)
menu.ui_interact(user)
return TRUE
/// Saves component-style GAGS color and component state.
/datum/loadout_item/proc/set_component_style_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
var/datum/greyscale_modify_menu/component_style/component_menu = open_menu
if(!istype(component_menu))
return FALSE
var/list/loadout = manager.get_current_loadout()
if(!loadout?[item_path])
return FALSE
var/list/colors = component_menu.split_colors
if(!colors)
return FALSE
loadout[item_path][INFO_GREYSCALE] = colors.Join("")
loadout[item_path][INFO_GREYSCALE_COMPONENT_CORES] = component_menu.selected_cores.Copy()
loadout[item_path][INFO_GREYSCALE_COMPONENT_ACCESSORIES] = length(component_menu.selected_accessories) ? component_menu.selected_accessories.Copy() : list(/datum/greyscale_component_style::no_accessories_marker)
manager.save_current_loadout(loadout)
return TRUE
/datum/loadout_item/proc/apply_component_style_to_equipped_item(obj/item/equipped_item, list/item_details)
if(equipped_item?.greyscale_component_style_type)
equipped_item.apply_greyscale_component_style(item_details)
/proc/get_component_style_item_preview_image(obj/item/item_path)
if(item_path == /obj/item/clothing/head/soft/sec/recolorable)
var/icon/preview_icon = icon('icons/obj/clothing/head/hats.dmi', "secsoft")
return icon2base64(preview_icon)
var/component_style_type = initial(item_path.greyscale_component_style_type)
if(!component_style_type)
return null
var/datum/greyscale_component_style/component_style = new component_style_type
var/list/default_cores = component_style.sanitize_core_selection(initial(item_path.greyscale_component_cores))
var/list/default_accessories = component_style.default_accessories
default_accessories = component_style.sanitize_accessories(default_accessories, default_cores)
var/list/default_colors = component_style.default_color_list(SSgreyscale.ParseColorString(initial(item_path.greyscale_colors)))
var/preview_icon_file = initial(item_path.greyscale_component_worn_icon_file) || initial(item_path.greyscale_component_icon_file)
var/list/preview_state_overrides
if(preview_icon_file == initial(item_path.greyscale_component_worn_icon_file))
preview_state_overrides = initial(item_path.greyscale_component_worn_state_overrides)
var/icon/south_preview_icon = component_style.build_icon(preview_icon_file, default_colors, default_cores, default_accessories, only_dir = SOUTH, state_overrides = preview_state_overrides)
var/icon/single_frame_preview = icon(south_preview_icon, component_style.output_icon_state, SOUTH, 1)
qdel(component_style)
return icon2base64(single_frame_preview)
/datum/loadout_item/proc/get_component_style_preview_image()
return get_component_style_item_preview_image(item_path)
/datum/loadout_item/proc/add_component_style_preview_to_ui_data(list/formatted_item)
if(!uses_greyscale_component_style())
return
formatted_item["image"] = get_component_style_preview_image()
/obj/machinery/vending/proc/get_component_style_vending_preview_image(obj/item/item_path)
return get_component_style_item_preview_image(item_path)
/obj/machinery/vending
/// Temporary component-style selections keyed by vending user ref while the normal vend path creates the item.
var/list/greyscale_component_style_vend_details
/obj/machinery/vending/proc/open_component_style_vending_menu(datum/data/vending_product/product, list/params, mob/user)
var/obj/item/item_path = product.product_path
var/list/allowed_configs = list()
if(initial(item_path.greyscale_config_worn))
allowed_configs += "[initial(item_path.greyscale_config_worn)]"
else if(initial(item_path.greyscale_config))
allowed_configs += "[initial(item_path.greyscale_config)]"
var/preview_icon_file = initial(item_path.greyscale_component_worn_icon_file) || initial(item_path.greyscale_component_icon_file)
var/list/preview_state_overrides
if(preview_icon_file == initial(item_path.greyscale_component_worn_icon_file))
preview_state_overrides = initial(item_path.greyscale_component_worn_state_overrides)
var/datum/greyscale_modify_menu/component_style/menu = new(
src,
user,
allowed_configs,
CALLBACK(src, PROC_REF(_vend_component_style), params, user),
starting_icon_state = initial(item_path.icon_state),
starting_config = initial(item_path.greyscale_config_worn) || initial(item_path.greyscale_config),
starting_colors = initial(item_path.greyscale_colors),
component_style_type = initial(item_path.greyscale_component_style_type),
selected_cores = initial(item_path.greyscale_component_cores),
selected_accessories = initial(item_path.greyscale_component_accessories),
preview_icon_file = preview_icon_file,
preview_digi_fallback_icon_file = initial(item_path.greyscale_component_digi_fallback_icon_file),
preview_state_overrides = preview_state_overrides,
default_colors = initial(item_path.greyscale_colors),
default_cores = initial(item_path.greyscale_component_cores),
default_accessories = initial(item_path.greyscale_component_accessories),
)
menu.ui_interact(user)
return TRUE
/obj/machinery/vending/proc/_vend_component_style(list/params, mob/user, datum/greyscale_modify_menu/menu)
PRIVATE_PROC(TRUE)
var/datum/greyscale_modify_menu/component_style/component_menu = menu
if(user != menu.user || !istype(component_menu))
return
var/list/component_details = list(
INFO_GREYSCALE_COMPONENT_CORES = component_menu.selected_cores.Copy(),
INFO_GREYSCALE_COMPONENT_ACCESSORIES = length(component_menu.selected_accessories) ? component_menu.selected_accessories.Copy() : list(/datum/greyscale_component_style::no_accessories_marker),
)
if(!greyscale_component_style_vend_details)
greyscale_component_style_vend_details = list()
var/user_ref = REF(user)
greyscale_component_style_vend_details[user_ref] = component_details
vend(params, user, component_menu.split_colors)
greyscale_component_style_vend_details -= user_ref
if(!length(greyscale_component_style_vend_details))
greyscale_component_style_vend_details = null
/datum/greyscale_modify_menu/component_style
var/datum/greyscale_component_style/component_style
var/component_style_type
var/list/selected_cores
var/list/selected_accessories
var/preview_icon_file
var/preview_use_digi = FALSE
var/preview_digi_fallback_icon_file
var/list/preview_state_overrides
var/list/default_colors
var/list/default_cores
var/list/default_accessories
/datum/greyscale_modify_menu/component_style/New(datum/target, client/user, list/allowed_configs, datum/callback/apply_callback, starting_icon_state = "", starting_config, starting_colors, unlocked = FALSE, component_style_type = null, list/selected_cores = null, list/selected_accessories = null, preview_icon_file = null, preview_use_digi = FALSE, preview_digi_fallback_icon_file = null, list/preview_state_overrides = null, default_colors = null, list/default_cores = null, list/default_accessories = null)
src.component_style_type = component_style_type || /datum/greyscale_component_style
var/style_type = src.component_style_type
src.component_style = new style_type
src.selected_cores = component_style.sanitize_core_selection(selected_cores)
if(isnull(selected_accessories))
selected_accessories = component_style.default_accessories
src.selected_accessories = component_style.sanitize_accessories(selected_accessories, src.selected_cores)
src.preview_icon_file = preview_icon_file
src.preview_use_digi = preview_use_digi
src.preview_digi_fallback_icon_file = preview_digi_fallback_icon_file
src.preview_state_overrides = preview_state_overrides
src.default_colors = component_style.default_color_list(SSgreyscale.ParseColorString(default_colors || starting_colors))
src.default_cores = component_style.sanitize_core_selection(default_cores)
if(isnull(default_accessories))
default_accessories = component_style.default_accessories
src.default_accessories = component_style.sanitize_accessories(default_accessories, src.default_cores)
sprite_data = list(
"icon_states" = list(component_style.output_icon_state),
"finished" = null,
"steps" = list(),
"time_spent" = 0,
)
update_component_color_labels()
return ..()
/datum/greyscale_modify_menu/component_style/Destroy()
QDEL_NULL(component_style)
selected_cores = null
selected_accessories = null
preview_state_overrides = null
default_colors = null
default_cores = null
default_accessories = null
return ..()
/datum/greyscale_modify_menu/component_style/ReadColorsFromString(colorString)
if(..())
return TRUE
normalize_split_colors()
return TRUE
/datum/greyscale_modify_menu/component_style/ui_data(mob/user)
normalize_split_colors()
var/list/data = list()
data["greyscale_config"] = "[config.name]"
data["full_color_string"] = split_colors.Join("")
var/list/active_color_ids = component_style.active_color_ids(selected_cores, selected_accessories)
var/list/active_colors = list()
for(var/i in 1 to config.expected_colors)
if(!(i in active_color_ids))
continue
active_colors += list(list(
"index" = i,
"label" = color_labels?["[i]"],
"value" = (length(split_colors) >= i) ? split_colors[i] : rgb(100, 100, 100),
))
data["colors"] = active_colors
data["generate_full_preview"] = generate_full_preview
data["unlocked"] = unlocked
data["refreshing"] = refreshing
data["monitoring_files"] = !!(config.datum_flags & DF_ISPROCESSING)
data["sprites_dir"] = dir2text(sprite_dir)
data["icon_state"] = icon_state
data["sprites"] = sprite_data || list(
"icon_states" = list(component_style.output_icon_state),
"finished" = null,
"steps" = list(),
"time_spent" = 0,
)
data["hide_full_color_string"] = FALSE
data["component_style"] = list(
"core_components" = component_style.core_ui_data(selected_cores),
"accessories" = component_style.accessory_ui_data(selected_accessories, selected_cores),
)
return data
/datum/greyscale_modify_menu/component_style/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
switch(action)
if("component_style_set_core")
var/core_component = params["component"]
var/new_style = params["value"]
if(!set_core_component(core_component, new_style))
return TRUE
queue_refresh()
return TRUE
if("component_style_toggle_accessory")
var/accessory = params["accessory"]
if(!component_style.accessory_allowed(accessory, selected_cores))
return TRUE
if(accessory in selected_accessories)
selected_accessories -= accessory
else
selected_accessories += accessory
update_component_color_labels()
queue_refresh()
return TRUE
if("random_all_colors")
for(var/color_id in component_style.active_color_ids(selected_cores, selected_accessories))
randomize_color(color_id)
queue_refresh()
return TRUE
if("component_style_reset_color")
var/color_id = text2num(params["color_index"])
if(color_id && length(default_colors) >= color_id)
normalize_split_colors()
split_colors[color_id] = default_colors[color_id]
queue_refresh()
return TRUE
if("component_style_reset_all")
selected_cores = default_cores.Copy()
selected_accessories = default_accessories.Copy()
split_colors = islist(default_colors) ? default_colors.Copy() : list()
normalize_split_colors()
update_component_color_labels()
queue_refresh()
return TRUE
return ..()
/datum/greyscale_modify_menu/component_style/proc/set_core_component(core_key, new_style)
for(var/list/core_component as anything in component_style.core_components)
if(core_component["key"] != core_key)
continue
if(!component_style.core_option_exists(core_component, new_style))
return FALSE
selected_cores[core_key] = new_style
prune_invalid_accessories()
update_component_color_labels()
return TRUE
return FALSE
/datum/greyscale_modify_menu/component_style/proc/prune_invalid_accessories()
selected_accessories = component_style.sanitize_accessories(selected_accessories, selected_cores)
/datum/greyscale_modify_menu/component_style/proc/update_component_color_labels()
color_labels = component_style.active_color_labels(selected_cores, selected_accessories)
/datum/greyscale_modify_menu/component_style/proc/normalize_split_colors()
if(!config)
return
if(!islist(split_colors))
split_colors = list()
if(length(split_colors) > config.expected_colors)
split_colors.Cut(config.expected_colors + 1)
while(length(split_colors) < config.expected_colors)
var/next_color_index = length(split_colors) + 1
split_colors += (islist(default_colors) && length(default_colors) >= next_color_index) ? default_colors[next_color_index] : rgb(100, 100, 100)
/datum/greyscale_modify_menu/component_style/refresh_preview()
normalize_split_colors()
var/list/used_colors = split_colors.Copy(1, config.expected_colors + 1)
sprite_data = list()
sprite_data["icon_states"] = list(component_style.output_icon_state)
icon_state = component_style.output_icon_state
var/time_spent = TICK_USAGE
var/icon/preview_icon = component_style.build_icon(preview_icon_file, used_colors, selected_cores, selected_accessories, preview_use_digi, preview_digi_fallback_icon_file, only_dir = sprite_dir, state_overrides = preview_state_overrides)
var/image/finished = image(preview_icon, icon_state = icon_state)
time_spent = TICK_USAGE - time_spent
sprite_data["time_spent"] = TICK_DELTA_TO_MS(time_spent)
sprite_data["finished"] = icon2html(finished, user, dir = sprite_dir, sourceonly = TRUE)
sprite_data["steps"] = list()
refreshing = FALSE
@@ -76,6 +76,22 @@
item_path = /obj/item/clothing/head/soft/sec
restricted_roles = list(ALL_JOBS_SEC)
/datum/loadout_item/head/security_cap/recolorable
name = "Recolorable Security Cap"
item_path = /obj/item/clothing/head/soft/sec/recolorable
restricted_roles = list(ALL_JOBS_SEC)
greyscale_component_preview_icon_file = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
/datum/loadout_item/head/security_cap/recolorable/set_item_color(datum/preference_middleware/loadout/manager, mob/user)
return set_component_style_item_color(manager, user)
/datum/loadout_item/head/security_cap/recolorable/set_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
return set_component_style_slot_greyscale(manager, open_menu)
/datum/loadout_item/head/security_cap/recolorable/on_equip_item(obj/item/equipped_item, list/item_details, mob/living/carbon/human/equipper, datum/outfit/outfit, visuals_only = FALSE)
. = ..()
apply_component_style_to_equipped_item(equipped_item, item_details)
/datum/loadout_item/head/security_cap/veteran
name = "Veteran's Soft Security Cap"
item_path = /obj/item/clothing/head/soft/veteran
@@ -80,6 +80,21 @@
item_path = /obj/item/clothing/under/rank/security/officer/redsec
restricted_roles = list(ALL_JOBS_SEC)
/datum/loadout_item/uniform/security/officer/recolorable
name = "Recolorable Security Uniform"
item_path = /obj/item/clothing/under/rank/security/officer/recolorable
restricted_roles = list(ALL_JOBS_SEC)
/datum/loadout_item/uniform/security/officer/recolorable/set_item_color(datum/preference_middleware/loadout/manager, mob/user)
return set_component_style_item_color(manager, user)
/datum/loadout_item/uniform/security/officer/recolorable/set_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
return set_component_style_slot_greyscale(manager, open_menu)
/datum/loadout_item/uniform/security/officer/recolorable/on_equip_item(obj/item/equipped_item, list/item_details, mob/living/carbon/human/equipper, datum/outfit/outfit, visuals_only = FALSE)
. = ..()
apply_component_style_to_equipped_item(equipped_item, item_details)
/datum/loadout_item/uniform/security/peacekeeper/armadyne
name = "Armadyne Corporate Uniform"
item_path =/obj/item/clothing/under/rank/security/peacekeeper/armadyne
@@ -13,6 +13,21 @@
icon_state = "security"
worn_icon_state = "security"
/obj/item/storage/belt/security/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_belt, infinite = FALSE)
/datum/atom_skin/sec_belt
abstract_type = /datum/atom_skin/sec_belt
/datum/atom_skin/sec_belt/standard
preview_name = "Standard"
/datum/atom_skin/sec_belt/black
preview_name = "Black"
new_icon = 'modular_zubbers/icons/obj/clothing/belts/belts.dmi'
new_worn_icon = 'modular_zubbers/icons/mob/clothing/belt.dmi'
new_icon_state = "security_black"
///Enables you to quickdraw weapons from security holsters
/datum/storage/security/open_storage(datum/source, mob/user)
var/atom/resolve_parent = parent
@@ -51,6 +66,21 @@
icon_state = "sunhudsec"
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/security/sunglasses/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_hud_sunglasses, infinite = FALSE)
/datum/atom_skin/sec_hud_sunglasses
abstract_type = /datum/atom_skin/sec_hud_sunglasses
/datum/atom_skin/sec_hud_sunglasses/standard
preview_name = "Standard"
/datum/atom_skin/sec_hud_sunglasses/sunglasses
preview_name = "Sunglasses"
new_icon = 'icons/obj/clothing/glasses.dmi'
new_worn_icon = 'icons/mob/clothing/eyes.dmi'
new_icon_state = "sun"
/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch
icon_state = "hudpatch"
base_icon_state = "hudpatch"
@@ -203,16 +233,37 @@
worn_icon = 'icons/mob/clothing/hands.dmi'
icon_state = "sec"
/obj/item/clothing/gloves/color/black/security/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_gloves, infinite = FALSE)
/obj/item/clothing/gloves/tackler/security //Can't just overwrite tackler, as there's a ton of subtypes that we'd then need to account for. This is easier. MUCH easier.
icon = 'icons/obj/clothing/gloves.dmi'
worn_icon = 'icons/mob/clothing/hands.dmi'
icon_state = "gorilla"
/obj/item/clothing/gloves/tackler/security/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_gloves, infinite = FALSE)
/obj/item/clothing/gloves/tackler/combat
icon = 'icons/obj/clothing/gloves.dmi'
worn_icon = 'icons/mob/clothing/hands.dmi'
icon_state = "gorilla"
/obj/item/clothing/gloves/tackler/combat/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_gloves, infinite = FALSE)
/datum/atom_skin/sec_gloves
abstract_type = /datum/atom_skin/sec_gloves
/datum/atom_skin/sec_gloves/standard
preview_name = "Standard"
/datum/atom_skin/sec_gloves/plain_black
preview_name = "Plain Black"
new_icon = 'modular_skyrat/master_files/icons/obj/clothing/gloves.dmi'
new_worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/hands.dmi'
new_icon_state = "combat"
/obj/item/clothing/gloves/kaza_ruk/sec
icon = 'modular_skyrat/master_files/icons/obj/clothing/gloves.dmi'
worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/hands.dmi'
@@ -646,12 +697,6 @@
inhand_icon_state = "security"
worn_icon_state = "security"
/obj/item/storage/belt/security/redsec/Initialize(mapload)
. = ..()
var/list/reskin_components = GetComponents(/datum/component/reskinable_item)
for(var/datum/component/reskinable_item/reskin_component as anything in reskin_components)
qdel(reskin_component)
/obj/item/storage/belt/holster
desc = "A rather plain but still cool looking holster that can hold a handgun, and some ammo."
@@ -29,6 +29,7 @@
/obj/item/clothing/head/security_kepi = 6,
/obj/item/clothing/head/beret/sec/peacekeeper/armadyne = 6,
/obj/item/clothing/head/soft/sec = 6,
/obj/item/clothing/head/soft/sec/recolorable = 6,
/obj/item/clothing/head/security_beanie = 6,
/obj/item/clothing/head/costume/ushanka/sec = 10,
/obj/item/clothing/head/playbunnyears/security = 6,
@@ -59,6 +60,7 @@
/obj/item/clothing/neck/security_cape = 6,
/obj/item/clothing/neck/security_cape/armplate = 6,
/obj/item/clothing/under/rank/security/officer = 6,
/obj/item/clothing/under/rank/security/officer/recolorable = 6,
/obj/item/clothing/under/rank/security/peacekeeper/skirt = 6,
/obj/item/clothing/under/rank/security/peacekeeper/miniskirt = 6,
/obj/item/clothing/under/rank/security/officer/blueshirt = 6,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+2
View File
@@ -9022,6 +9022,7 @@
#include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\warrior.dm"
#include "modular_zubbers\code\__DEFINES\bloodsucker_defines.dm"
#include "modular_zubbers\code\__DEFINES\guardian_defines.dm"
#include "modular_zubbers\code\__DEFINES\loadout.dm"
#include "modular_zubbers\code\__DEFINES\maps.dm"
#include "modular_zubbers\code\__DEFINES\moonstation_defines.dm"
#include "modular_zubbers\code\__DEFINES\projectiles.dm"
@@ -9759,6 +9760,7 @@
#include "modular_zubbers\code\modules\general_sprite_overrides\turfs\walls.dm"
#include "modular_zubbers\code\modules\ghost_wavedefence\wave_def.dm"
#include "modular_zubbers\code\modules\gladiator\code\game\objects\items\gladiator_items.dm"
#include "modular_zubbers\code\modules\greyscale\component_style.dm"
#include "modular_zubbers\code\modules\hydroponics\gene_modder.dm"
#include "modular_zubbers\code\modules\hydroponics\plant_genes.dm"
#include "modular_zubbers\code\modules\hydroponics\grown\rocks.dm"
@@ -18,6 +18,7 @@ import { Window } from '../layouts';
type ColorEntry = {
index: number;
label?: string; //BUBBER ADDITION - dynamic uniforms
value: string;
};
@@ -37,6 +38,11 @@ type SpriteEntry = {
type GreyscaleMenuData = {
greyscale_config: string;
colors: ColorEntry[];
//BUBBER ADDITION START - dynamic uniforms
full_color_string?: string;
hide_full_color_string?: boolean;
component_style?: ComponentStyleData;
//BUBBER ADDITION END - dynamic uniforms
sprites: SpriteData;
generate_full_preview: boolean;
unlocked: boolean;
@@ -46,6 +52,25 @@ type GreyscaleMenuData = {
refreshing: boolean;
};
//BUBBER ADDITION START - dynamic uniforms
type ComponentStyleOption = {
name: string;
value: string;
selected: boolean;
};
type ComponentStyleCore = {
name: string;
key: string;
options: ComponentStyleOption[];
};
type ComponentStyleData = {
core_components: ComponentStyleCore[];
accessories: ComponentStyleOption[];
};
//BUBBER ADDITION END - dynamic uniforms
enum Direction {
North = 'north',
NorthEast = 'northeast',
@@ -86,6 +111,68 @@ const ConfigDisplay = (props) => {
</Section>
);
};
//BUBBER ADDITION START - dynamic uniforms
const ComponentStyleDisplay = (props) => {
const { act, data } = useBackend<GreyscaleMenuData>();
const style = data.component_style;
if (!style) {
return null;
}
return (
<Section title="Style">
<LabeledList>
{style.core_components.map((component) => (
<LabeledList.Item key={component.key} label={component.name}>
<Flex wrap>
{component.options.map((item) => (
<Flex.Item
key={`${component.key}-${item.value}`}
mr={0.5}
mb={0.5}
>
<Button.Checkbox
checked={item.selected}
onClick={() =>
act('component_style_set_core', {
component: component.key,
value: item.value,
})
}
>
{item.name}
</Button.Checkbox>
</Flex.Item>
))}
</Flex>
</LabeledList.Item>
))}
{!!style.accessories.length && (
<LabeledList.Item label="Accessories">
<Flex wrap>
{style.accessories.map((item) => (
<Flex.Item key={item.value} mr={0.5} mb={0.5}>
<Button.Checkbox
checked={item.selected}
onClick={() =>
act('component_style_toggle_accessory', {
accessory: item.value,
})
}
>
{item.name}
</Button.Checkbox>
</Flex.Item>
))}
</Flex>
</LabeledList.Item>
)}
</LabeledList>
</Section>
);
};
//BUBBER ADDITION END - dynamic uniforms
const ColorDisplay = (props) => {
const { act, data } = useBackend<GreyscaleMenuData>();
@@ -93,23 +180,37 @@ const ColorDisplay = (props) => {
return (
<Section title="Colors">
<LabeledList>
<LabeledList.Item label="Full Color String">
<Button
icon="dice"
onClick={() => act('random_all_colors')}
tooltip="Randomizes all color groups."
/>
<Input
value={colors.map((item) => item.value).join('')}
onBlur={(value) =>
act('recolor_from_string', { color_string: value })
}
/>
</LabeledList.Item>
{/* BUBBER EDIT START - dynamic uniforms */}
{!data.hide_full_color_string && (
<LabeledList.Item label="Full Color String">
<Button
icon="dice"
onClick={() => act('random_all_colors')}
tooltip="Randomizes all color groups."
/>
{!!data.component_style && (
<Button
icon="undo"
onClick={() => act('component_style_reset_all')}
tooltip="Resets colors and style options to their defaults."
/>
)}
<Input
value={
data.full_color_string ||
colors.map((item) => item.value).join('')
}
onBlur={(value) =>
act('recolor_from_string', { color_string: value })
}
/>
</LabeledList.Item>
)}
{/* BUBBER EDIT END - dynamic uniforms */}
{colors.map((item) => (
<LabeledList.Item
key={`colorgroup${item.index}${item.value}`}
label={`Color Group ${item.index}`}
label={item.label || `Color Group ${item.index}`} //BUBBER EDIT
color={item.value}
>
<ColorBox color={item.value} />{' '}
@@ -123,6 +224,19 @@ const ColorDisplay = (props) => {
onClick={() => act('random_color', { color_index: item.index })}
tooltip="Randomizes the color for this color group."
/>
{/* BUBBER EDIT START - dynamic uniforms */}
{!!data.component_style && (
<Button
icon="undo"
onClick={() =>
act('component_style_reset_color', {
color_index: item.index,
})
}
tooltip="Resets this color group to its default."
/>
)}
{/* BUBBER EDIT END - dynamic uniforms */}
<Input
value={item.value}
width={7}
@@ -284,6 +398,9 @@ export const GreyscaleModifyMenu = (props) => {
<Window title="Color Configuration" width={325} height={800}>
<Window.Content scrollable>
<ConfigDisplay />
{/* BUBBER ADDITION START - dynamic uniforms */}
<ComponentStyleDisplay />
{/* BUBBER ADDITION END - dynamic uniforms */}
<ColorDisplay />
<IconStatesDisplay />
<Flex direction="column">
@@ -21,6 +21,20 @@ export function ItemIcon(props: Props) {
const { item, scale = 3 } = props;
const icon_to_use = item.icon;
const icon_state_to_use = item.icon_state;
//BUBBER EDIT START - dyamic uniforms
if (item.image) {
return (
<img
src={`data:image/png;base64,${item.image}`}
style={{
width: `${32 * scale}px`,
height: `${32 * scale}px`,
imageRendering: 'pixelated',
}}
/>
);
}
//BUBBER EDIT END - dynamic uniforms
if (!icon_to_use || !icon_state_to_use) {
return (
@@ -67,6 +81,7 @@ export function ItemDisplay(props: DisplayProps) {
style={{ textTransform: 'capitalize', zIndex: '1' }}
tooltip={item.name}
tooltipPosition={'bottom'}
base64={item.image} //BUBBER EDIT ADDITION: Dynamic uniform icons
dmIcon={item.icon}
dmIconState={item.icon_state}
onClick={() =>
@@ -36,6 +36,7 @@ export type LoadoutItem = {
path: typePath;
icon: DmIconFile | null;
icon_state: DmIconState | null;
image?: string;
buttons: LoadoutButton[];
reskins: ReskinOption[] | null;
information: LoadoutTooltip[];