diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 56cf681a27..ff0750a28c 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -255,6 +255,7 @@
#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user)
#define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args)
#define COMSIG_ITEM_WEARERCROSSED "wearer_crossed" //called on item when crossed by something (): (/atom/movable)
+#define COMSIG_ITEM_WORN_OVERLAYS "item_worn_overlays" //from base of obj/item/worn_overlays(): (isinhands, icon_file, style_flags, list/overlays)
// /obj/item/clothing signals
#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): ()
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index f16b566b00..8887a2aa1d 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -550,6 +550,27 @@
for(var/thing in flat_list)
.[thing] = TRUE
+/proc/deep_list2params(list/deep_list)
+ var/list/L = list()
+ for(var/i in deep_list)
+ var/key = i
+ if(isnum(key))
+ key = "[key]"
+ continue
+ if(islist(key))
+ key = deep_list2params(key)
+ else if(!istext(key))
+ key = "[REF(key)]"
+ L += "[key]"
+ var/value = deep_list[key]
+ if(!isnull(value))
+ if(islist(value))
+ value = deep_list2params(value)
+ else if(!(istext(key) || isnum(key)))
+ value = "[REF(value)]"
+ L["[key]"] = "[value]"
+ return list2params(L)
+
//Picks from the list, with some safeties, and returns the "default" arg if it fails
#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default)
diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/dcs.dm
index faf95fd319..19a1ac26b5 100644
--- a/code/controllers/subsystem/dcs.dm
+++ b/code/controllers/subsystem/dcs.dm
@@ -16,7 +16,10 @@ PROCESSING_SUBSYSTEM_DEF(dcs)
if(istext(argument) || isnum(argument))
fullid += "[argument]"
else
- fullid += "[REF(argument)]"
+ if(islist(argument))
+ fullid += deep_list2params(argument)
+ else
+ fullid += "[REF(argument)]"
element_id = fullid.Join("&")
. = elements_by_type[element_id]
diff --git a/code/datums/action.dm b/code/datums/action.dm
index f1df2a80d8..65db324b2b 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -6,13 +6,15 @@
/datum/action
var/name = "Generic Action"
var/desc = null
- var/obj/target = null
+ var/atom/target = null
var/check_flags = 0
var/required_mobility_flags = MOBILITY_USE
var/processing = FALSE
var/obj/screen/movable/action_button/button = null
var/buttontooltipstyle = ""
var/transparent_when_unavailable = TRUE
+ var/use_target_appearance = FALSE
+ var/list/target_appearance_matrix //if set, will be used to transform the target button appearance as an arglist.
var/button_icon = 'icons/mob/actions/backgrounds.dmi' //This is the file for the BACKGROUND icon
var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND //And this is the state for the background icon
@@ -88,7 +90,7 @@
/datum/action/proc/Trigger()
if(!IsAvailable())
return FALSE
- if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
+ if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, target) & COMPONENT_ACTION_BLOCK_TRIGGER)
return FALSE
return TRUE
@@ -116,7 +118,9 @@
return TRUE
/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
- if(button)
+ if(!button)
+ return
+ if(button_icon && button_icon_state)
if(!status_only)
button.name = name
button.desc = desc
@@ -134,11 +138,22 @@
ApplyIcon(button, force)
- if(!IsAvailable())
- button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
- else
- button.color = rgb(255,255,255,255)
- return 1
+ else if(use_target_appearance && target && button.appearance_cache != target.appearance) //replace with /ref comparison if this is not valid.
+ var/mutable_appearance/M = new(target)
+ M.layer = FLOAT_LAYER
+ M.plane = FLOAT_PLANE
+ if(target_appearance_matrix)
+ var/list/L = target_appearance_matrix
+ M.transform = matrix(L[1], L[2], L[3], L[4], L[5], L[6])
+ button.cut_overlays()
+ button.add_overlay(M)
+ button.appearance_cache = target.appearance
+
+ if(!IsAvailable())
+ button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
+ else
+ button.color = rgb(255,255,255,255)
+ return 1
/datum/action/proc/ApplyIcon(obj/screen/movable/action_button/current_button, force = FALSE)
if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
@@ -165,6 +180,7 @@
/datum/action/item_action
check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_LYING|AB_CHECK_CONSCIOUS
button_icon_state = null
+ use_target_appearance = TRUE
// If you want to override the normal icon being the item
// then change this to an icon state
@@ -188,23 +204,6 @@
I.ui_action_click(owner, src)
return 1
-/datum/action/item_action/ApplyIcon(obj/screen/movable/action_button/current_button, force)
- if(button_icon && button_icon_state)
- // If set, use the custom icon that we set instead
- // of the item appearence
- ..()
- else if(target && current_button.appearance_cache != target.appearance) //replace with /ref comparison if this is not valid.
- var/obj/item/I = target
- var/old_layer = I.layer
- var/old_plane = I.plane
- I.layer = FLOAT_LAYER //AAAH
- I.plane = FLOAT_PLANE //^ what that guy said
- current_button.cut_overlays()
- current_button.add_overlay(I)
- I.layer = old_layer
- I.plane = old_plane
- current_button.appearance_cache = I.appearance
-
/datum/action/item_action/toggle_light
name = "Toggle Light"
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index ea72a8a6dd..3ed66dcf92 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -790,7 +790,7 @@
user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].")
return TRUE
-/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source)
+/datum/component/storage/proc/action_trigger(datum/action/source, obj/target)
gather_mode_switch(source.owner)
return COMPONENT_ACTION_BLOCK_TRIGGER
diff --git a/code/datums/components/virtual_reality.dm b/code/datums/components/virtual_reality.dm
index 63e4f4f092..cd5820d21f 100644
--- a/code/datums/components/virtual_reality.dm
+++ b/code/datums/components/virtual_reality.dm
@@ -174,7 +174,7 @@
/**
*The following procs simply acts as hooks for quit(), since components do not use callbacks anymore
*/
-/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
+/datum/component/virtual_reality/proc/action_trigger(datum/action/source, obj/target)
quit()
return COMPONENT_ACTION_BLOCK_TRIGGER
diff --git a/code/datums/elements/polychromic.dm b/code/datums/elements/polychromic.dm
new file mode 100644
index 0000000000..4301a280f3
--- /dev/null
+++ b/code/datums/elements/polychromic.dm
@@ -0,0 +1,127 @@
+#define POLYCHROMIC_ALTCLICK (1<<0)
+#define POLYCHROMIC_ACTION (1<<1)
+#define POLYCHROMIC_NO_HELD (1<<2)
+#define POLYCHROMIC_NO_WORN (1<<3)
+
+/datum/element/polychromic
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 3
+ var/list/overlays_by_atom = list()
+ var/list/overlays_states //also used for worn/held overlsays
+ var/icon_file
+ var/list/overlays_names //wrap numbers into text strings please.
+ var/list/actions_by_atom = list()
+ var/poly_flags
+ //item variables
+ var/worn_file //used for boths held and worn overlays if present.
+
+/datum/element/polychromic/Attach(datum/target, list/colors, list/states, _icon, _flags = POLYCHROMIC_ALTCLICK|POLYCHROMIC_NO_HELD, _worn, list/names = list("Primary", "Secondary", "Tertiary", "Quaternary", "Quinary", "Senary"))
+ . = ..()
+ var/states_len = length(overlays_states)
+ var/names_len = length(names)
+ if(!states_len || names_len || !isatom(target))
+ return ELEMENT_INCOMPATIBLE
+ var/atom/A = target
+
+ overlays_states = states
+ icon_file = _icon
+ worn_file = _worn
+ poly_flags = _flags
+
+ var/mut_icon = icon_file || A.icon
+ var/list/L = list()
+ for(var/I in overlays_states)
+ var/col = popleft(colors) || "#FFFFFF"
+ L += mutable_appearance(mut_icon, I, color = col)
+ A.add_overlay(L)
+ overlays_by_atom[A] = L
+
+ if(_flags & POLYCHROMIC_ALTCLICK)
+ RegisterSignal(A, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+ RegisterSignal(A, COMSIG_CLICK_ALT, .proc/set_color)
+
+ if(!overlays_names && names)
+ overlays_names = names
+ var/diff = states_len - names_len
+ if(diff > 0) //It will be ugly, but still functional.
+ for(var/i in 1 to diff)
+ overlays_names += "[names_len + i]"
+ else if(diff < 0)
+ overlays_names.len += diff
+
+ if(isitem(A))
+ if(_flags & POLYCHROMIC_ACTION)
+ RegisterSignal(src, COMSIG_ITEM_EQUIPPED, .proc/grant_user_action)
+ RegisterSignal(src, COMSIG_ITEM_DROPPED, .proc/remove_user_action)
+ AddElement(A, /datum/element/update_icon_updates_onmob) //Since we can change the overall aspect of the item.
+ RegisterSignal(A, COMSIG_ITEM_WORN_OVERLAYS, .proc/apply_worn_overlays)
+ else if(_flags & POLYCHROMIC_ACTION && ismob(A)) //Not safe until mob icon updating procs are standarized and stop using cut_overlays()
+ var/datum/action/polychromic/P = new(A)
+ RegisterSignal(P, COMSIG_ACTION_TRIGGER, .proc/activate_action)
+ actions_by_atom[A] = P
+ P.Grant(A)
+
+/datum/element/polychromic/Detach(atom/A)
+ . = ..()
+ A.cut_overlay(overlays_by_atom[A])
+ overlays_by_atom -= A
+ var/datum/action/polychromic/P = actions_by_atom[A]
+ if(P)
+ qdel(P)
+ actions_by_atom -= A
+ if(poly_flags & POLYCHROMIC_ALTCLICK)
+ UnregisterSignal(A, list(COMSIG_PARENT_EXAMINE, COMSIG_CLICK_ALT))
+
+/datum/element/polychromic/proc/apply_worn_overlays(obj/item/source, isinhands, icon_file, style_flags, list/overlays)
+ if(poly_flags & (isinhands ? POLYCHROMIC_NO_HELD : POLYCHROMIC_NO_WORN))
+ return
+ var/f_icon = worn_file || icon_file
+ var/list/L = overlays_by_atom[source]
+
+ for(var/I in 1 to length(overlays_states))
+ var/mutable_appearance/M = L[I]
+ overlays += mutable_appearance(f_icon, overlays_states[I], color = M.color)
+
+/datum/element/polychromic/proc/set_color(atom/source, mob/user)
+ var/choice = input(user,"Polychromic options", "Recolor [source]") as null|anything in overlays_names
+ if(!choice || QDELETED(source) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
+ return
+ choice = overlays_names.Find(choice)
+ var/ncolor = input(user, "Polychromic options", "Choose [choice] Color") as color|null
+ if(!ncolor || QDELETED(source) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
+ return
+ var/list/L = overlays_by_atom[source]
+ if(!L) // Ummmmmh.
+ return
+ var/mutable_appearance/M = L[choice]
+ M.color = sanitize_hexcolor(ncolor, 6, TRUE, M.color)
+ source.update_icon()
+ return TRUE
+
+/datum/element/polychromic/proc/grant_user_action(atom/source, mob/user, slot)
+ if(slot == SLOT_IN_BACKPACK || slot == SLOT_LEGCUFFED || slot == SLOT_HANDCUFFED || slot == SLOT_GENERC_DEXTROUS_STORAGE)
+ return
+ var/datum/action/polychromic/P = actions_by_atom[source]
+ if(!P)
+ P = new (source)
+ actions_by_atom[source] = P
+ P.check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_LYING|AB_CHECK_CONSCIOUS
+ RegisterSignal(P, COMSIG_ACTION_TRIGGER, .proc/activate_action)
+ P.Grant(user)
+
+/datum/element/polychromic/proc/remove_user_action(atom/source, mob/user)
+ var/datum/action/polychromic/P = actions_by_atom[source]
+ P?.Remove(user)
+
+/datum/element/polychromic/proc/activate_action(datum/action/source, atom/target)
+ set_color(target, source.owner)
+
+/datum/element/polychromic/proc/on_examine(atom/source, mob/user, list/examine_list)
+ examine_list += "Alt-click to recolor it."
+
+/datum/action/polychromic
+ name = "Modify Polychromic Colors"
+ background_icon_state = "bg_polychromic"
+ use_target_appearance = TRUE
+ button_icon_state = null
+ target_appearance_matrix = list(0.7,0,0,0,0.7,0)
diff --git a/code/datums/elements/update_icon_updates_onmob.dm b/code/datums/elements/update_icon_updates_onmob.dm
index ca0e8b1641..5c71547f62 100644
--- a/code/datums/elements/update_icon_updates_onmob.dm
+++ b/code/datums/elements/update_icon_updates_onmob.dm
@@ -5,7 +5,7 @@
. = ..()
if(!istype(target, /obj/item))
return ELEMENT_INCOMPATIBLE
- RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, .proc/update_onmob)
+ RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, .proc/update_onmob, override = TRUE)
/datum/element/update_icon_updates_onmob/proc/update_onmob(obj/item/target)
if(ismob(target.loc))
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index fd6ae1bb43..7a155ca932 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -425,6 +425,13 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
item_flags |= IN_INVENTORY
user.update_equipment_speed_mods()
+//Overlays for the worn overlay so you can overlay while you overlay
+//eg: ammo counters, primed grenade flashing, etc.
+//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file
+/obj/item/proc/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
+ . = list()
+ SEND_SIGNAL(src, COMSIG_ITEM_WORN_OVERLAYS, isinhands, icon_file, style_flags, .)
+
//sometimes we only want to grant the item's action if it's equipped in a specific slot.
/obj/item/proc/item_action_slot_check(slot, mob/user, datum/action/A)
if(slot == SLOT_IN_BACKPACK || slot == SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index e5483e8d73..0d6195e4dd 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -195,15 +195,6 @@
/mob/living/carbon/proc/update_hud_back(obj/item/I)
return
-
-
-//Overlays for the worn overlay so you can overlay while you overlay
-//eg: ammo counters, primed grenade flashing, etc.
-//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file
-/obj/item/proc/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
- . = list()
-
-
/mob/living/carbon/update_body()
update_body_parts()
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index 3697fe4ff5..07839588ce 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 7ce9569f9a..b12baa9c47 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -501,6 +501,7 @@
#include "code\datums\elements\firestacker.dm"
#include "code\datums\elements\ghost_role_eligibility.dm"
#include "code\datums\elements\mob_holder.dm"
+#include "code\datums\elements\polychromic.dm"
#include "code\datums\elements\swimming.dm"
#include "code\datums\elements\sword_point.dm"
#include "code\datums\elements\update_icon_blocker.dm"