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"