diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 7679d482bff..b01406bf631 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -24,14 +24,12 @@ #define BLOCKS_CONSTRUCTION_DIR (1<<10) /// Can we ignore density when building on this object (for example, directional windows and grilles) #define IGNORE_DENSITY (1<<11) -/// We can reskin this item infinitely -#define INFINITE_RESKIN (1<<12) /// Can this object conduct electricity -#define CONDUCTS_ELECTRICITY (1<<13) +#define CONDUCTS_ELECTRICITY (1<<12) /// Atoms don't spawn anything when deconstructed (they just vanish) -#define NO_DEBRIS_AFTER_DECONSTRUCTION (1<<14) +#define NO_DEBRIS_AFTER_DECONSTRUCTION (1<<13) /// Flag which tells an object to hang onto an support atom on late initialize. Usefull only during mapload and supported by some atoms only -#define MOUNT_ON_LATE_INITIALIZE (1<<15) +#define MOUNT_ON_LATE_INITIALIZE (1<<14) // If you add new ones, be sure to add them to /obj/Initialize as well for complete mapping support diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 42e4d3869d2..1ae8403c644 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -164,8 +164,6 @@ #define LOADOUT_FLAG_GREYSCALING_ALLOWED (1<<2) /// Allows the item to be renamed by the player. #define LOADOUT_FLAG_ALLOW_NAMING (1<<3) -/// Allows the item to be reskinned by the player. Only applicable to items with unique_reskin defined. -#define LOADOUT_FLAG_ALLOW_RESKIN (1<<4) // Loadout item info keys // Changing these will break existing loadouts diff --git a/code/__DEFINES/reskin.dm b/code/__DEFINES/reskin.dm new file mode 100644 index 00000000000..250b3959fe2 --- /dev/null +++ b/code/__DEFINES/reskin.dm @@ -0,0 +1,18 @@ +/// Global list of available atom skins +GLOBAL_LIST_INIT_TYPED(atom_skins, /datum/atom_skin, init_subtypes_w_path_keys(/datum/atom_skin)) + +/// Sets the atom's varname to newvalue if newvalue is not null, otherwise resets it to its initial value if resetcondition is true +#define APPLY_VAR_OR_RESET_INITIAL(atom, varname, newvalue, resetcondition) \ + if(newvalue) {atom.##varname = (##newvalue) } else if(resetcondition) { atom.##varname = initial(atom.##varname) } + +/// Sets the atom's varname to newvalue if newvalue is not null, otherwise sets it to resetvalue if resetcondition is true +#define APPLY_VAR_OR_RESET_TO(atom, varname, newvalue, resetcondition, resetvalue) \ + if(newvalue) {atom.##varname = (##newvalue) } else if(resetcondition) { atom.##varname = (resetvalue) } + +/// Resets the atom's varname to its initial value if oldvalue is not null +#define RESET_INITIAL_IF_SET(atom, varname, oldvalue) \ + if(oldvalue) { atom.##varname = initial(atom.##varname) } + +/// Sets the atom's varname to resetvalue if oldvalue is not null +#define RESET_TO_IF_SET(atom, varname, oldvalue, resetvalue) \ + if(oldvalue) { atom.##varname = (resetvalue) } diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 4f1f2ca0d3d..d5638e0312b 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -137,29 +137,21 @@ for(var/atom/req_atom as anything in recipe.structures) atom_list |= req_atom -//creates every subtype of prototype (excluding prototype) and adds it to list L. -//if no list/L is provided, one is created. +/// Creates every subtype of prototype (excluding prototype and abstract types) and adds it to list L. +/// If no list/L is provided, one is created. /proc/init_subtypes(prototype, list/L) if(!istype(L)) L = list() - for(var/path in subtypesof(prototype)) + for(var/path in valid_subtypesof(prototype)) L += new path() return L -//returns a list of paths to every subtype of prototype (excluding prototype) -//if no list/L is provided, one is created. -/proc/init_paths(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L+= path - return L - /// Functions like init_subtypes, but uses the subtype's path as a key for easy access +/// If no list/L is provided, one is created. /proc/init_subtypes_w_path_keys(prototype, list/L) if(!istype(L)) L = list() - for(var/path in subtypesof(prototype)) + for(var/path in valid_subtypesof(prototype)) L[path] = new path() return L diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 400b0609122..6be87df0909 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -361,7 +361,6 @@ DEFINE_BITFIELD(obj_flags, list( "BLOCKS_CONSTRUCTION" = BLOCKS_CONSTRUCTION, "BLOCKS_CONSTRUCTION_DIR" = BLOCKS_CONSTRUCTION_DIR, "IGNORE_DENSITY" = IGNORE_DENSITY, - "INFINITE_RESKIN" = INFINITE_RESKIN, "CONDUCTS_ELECTRICITY" = CONDUCTS_ELECTRICITY, "NO_DEBRIS_AFTER_DECONSTRUCTION" = NO_DEBRIS_AFTER_DECONSTRUCTION, )) diff --git a/code/datums/components/dart_insert.dm b/code/datums/components/dart_insert.dm index 9770de669b5..f2816949c9d 100644 --- a/code/datums/components/dart_insert.dm +++ b/code/datums/components/dart_insert.dm @@ -60,9 +60,9 @@ add_to_dart(dart, user) return COMPONENT_CANCEL_ATTACK_CHAIN -/datum/component/dart_insert/proc/on_reskin(datum/source, mob/user, skin) +/datum/component/dart_insert/proc/on_reskin(datum/source, skin) SIGNAL_HANDLER - SEND_SIGNAL(parent, COMSIG_DART_INSERT_PARENT_RESKINNED) + SEND_SIGNAL(parent, COMSIG_DART_INSERT_PARENT_RESKINNED, skin) /datum/component/dart_insert/proc/add_to_dart(obj/item/ammo_casing/dart, mob/user) var/obj/projectile/dart_projectile = dart.loaded_projectile diff --git a/code/datums/components/reskinnable_atom.dm b/code/datums/components/reskinnable_atom.dm new file mode 100644 index 00000000000..a14563bd630 --- /dev/null +++ b/code/datums/components/reskinnable_atom.dm @@ -0,0 +1,202 @@ +/** + * ### Atom skin singleton datum + * + * Simple datum which holds information about a skin that can be applied to an atom. + */ +/datum/atom_skin + abstract_type = /datum/atom_skin + + /// Required, name shown in the radial menu + var/preview_name + + /// If true, changing the reskin also changes the base_icon_state of the atom + var/change_base_icon_state = FALSE + /// If true, changing the reskin also changes the inhand_icon_state of the atom + var/change_inhand_icon_state = FALSE + /// If true, unset vars are reset to their original values when applying this skin + var/reset_missing = TRUE + + /// Optional, name to change the atom to when applied + var/new_name + /// Optional, description to change the atom to when applied + var/new_desc + /// Optional, icon to change the atom to when applied + var/new_icon + /// Optional, icon_state to change the atom to when applied + var/new_icon_state + +/** + * Applies all relevant skin changes to the given atom + * Can be overridden to add additional behavior, such as registering signals or altering other vars. + * + * * apply_to: The atom to apply the skin to + */ +/datum/atom_skin/proc/apply(atom/apply_to) + SHOULD_CALL_PARENT(TRUE) + APPLY_VAR_OR_RESET_INITIAL(apply_to, name, new_name, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, desc, new_desc, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, icon, new_icon, reset_missing) + APPLY_VAR_OR_RESET_TO(apply_to, icon_state, new_icon_state, reset_missing, initial(apply_to.post_init_icon_state) || initial(apply_to.icon_state)) + if(change_base_icon_state) + APPLY_VAR_OR_RESET_INITIAL(apply_to, base_icon_state, new_icon_state, reset_missing) + if(change_inhand_icon_state && isitem(apply_to)) + var/obj/item/item_apply_to = apply_to + APPLY_VAR_OR_RESET_INITIAL(item_apply_to, inhand_icon_state, new_icon_state, reset_missing) + +/** + * Resets all changes this skin would have made to the given atom + * Does not verify that the skin was actually applied to the atom beforehand. + * Can be overridden to add additional behavior, such as unregistering signals or altering other vars. + * + * * clear_from: The atom to clear the skin from + */ +/datum/atom_skin/proc/clear_skin(atom/clear_from) + SHOULD_CALL_PARENT(TRUE) + RESET_INITIAL_IF_SET(clear_from, name, new_name) + RESET_INITIAL_IF_SET(clear_from, desc, new_desc) + RESET_INITIAL_IF_SET(clear_from, icon, new_icon) + RESET_TO_IF_SET(clear_from, icon_state, new_icon_state, initial(clear_from.post_init_icon_state) || initial(clear_from.icon_state)) + if(change_base_icon_state) + RESET_INITIAL_IF_SET(clear_from, base_icon_state, new_icon_state) + if(change_inhand_icon_state && isitem(clear_from)) + var/obj/item/item_clear_from = clear_from + RESET_INITIAL_IF_SET(item_clear_from, inhand_icon_state, new_icon_state) + +/** + * ### Reskinnable atoms + * + * Simple component which lets an atom be alt-clicked to open a radial menu to choose a new skin to apply. + */ +/datum/component/reskinable_item + dupe_mode = COMPONENT_DUPE_SELECTIVE + /// Base reskin type to pull options from - all subtypes except those blacklisted are valid options + VAR_PRIVATE/base_reskin_type + /// If TRUE, the reskin option is infinite-use. If FALSE, the component is deleted on use (so you're stuck with that skin). + VAR_PRIVATE/infinite_reskin = FALSE + /// List of subtypes of /datum/atom_skin that are not allowed to be used for this item + VAR_PRIVATE/list/blacklisted_subtypes + + /// Currently applied skin preview_name + VAR_PRIVATE/current_skin + +/datum/component/reskinable_item/Initialize(base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes = list()) + if(!isatom(parent) || isarea(parent)) + return COMPONENT_INCOMPATIBLE + + src.base_reskin_type = base_reskin_type + src.infinite_reskin = infinite + src.blacklisted_subtypes = blacklisted_subtypes + + if(initial_skin) + set_skin_by_name(initial_skin) + + var/atom/atom_parent = parent + atom_parent.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1 + +/datum/component/reskinable_item/RegisterWithParent() + RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(on_click_alt_reskin)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(add_tags)) + RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(add_context)) + +/datum/component/reskinable_item/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_CLICK_ALT) + UnregisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS) + UnregisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM) + +/datum/component/reskinable_item/CheckDupeComponent(datum/component/comp, base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes = list()) + if(src.base_reskin_type != base_reskin_type) + return FALSE // new comp - though the alt-click behavior will collide + + src.infinite_reskin = infinite + src.blacklisted_subtypes = blacklisted_subtypes + + set_skin_by_name(initial_skin) + return TRUE // same comp + +/datum/component/reskinable_item/proc/get_skins_by_name() + var/list/reskin_options = list() + for(var/datum/atom_skin/reskin_option as anything in valid_subtypesof(base_reskin_type) - blacklisted_subtypes) + reskin_options[reskin_option::preview_name] = reskin_option + + return reskin_options + +/datum/component/reskinable_item/proc/set_skin_by_name(input_name) + var/list/reskin_options = get_skins_by_name() + if(current_skin) + var/datum/atom_skin/previous_skin = GLOB.atom_skins[reskin_options[current_skin]] + previous_skin.clear_skin(parent) + + if(input_name) + var/datum/atom_skin/reskin_to_apply = GLOB.atom_skins[reskin_options[input_name]] + reskin_to_apply.apply(parent) + + current_skin = input_name + + var/atom/atom_parent = parent + atom_parent.update_appearance() + + if(isitem(parent)) + var/obj/item/item_parent = parent + item_parent.update_slot_icon() + + SEND_SIGNAL(parent, COMSIG_OBJ_RESKIN, input_name) + +/datum/component/reskinable_item/proc/add_context(atom/source, list/context, obj/item/held_item, mob/user) + SIGNAL_HANDLER + + context[SCREENTIP_CONTEXT_ALT_LMB] = "Reskin" + return CONTEXTUAL_SCREENTIP_SET + +/datum/component/reskinable_item/proc/add_tags(atom/source, mob/user, list/tags) + SIGNAL_HANDLER + + tags["reskinnable"] = "This item is able to be reskinned! Alt-Click to do so!" + +/// Called when alt clicked and the item has unique reskin options +/datum/component/reskinable_item/proc/on_click_alt_reskin(datum/source, mob/user) + SIGNAL_HANDLER + + if(!user.can_perform_action(parent, NEED_DEXTERITY)) + return NONE + + INVOKE_ASYNC(src, PROC_REF(reskin_obj), user) + return CLICK_ACTION_SUCCESS + +/** + * Reskins object based on a user's choice + * + * Arguments: + * * user The mob choosing a reskin option + */ +/datum/component/reskinable_item/proc/reskin_obj(mob/user) + var/atom/atom_parent = parent + + var/list/items = list() + for(var/reskin_name, reskin_typepath in get_skins_by_name()) + var/datum/atom_skin/reskin = GLOB.atom_skins[reskin_typepath] + items[reskin_name] = image(icon = reskin.new_icon || atom_parent.icon, icon_state = reskin.new_icon_state || atom_parent.icon_state) + + sort_list(items) + + var/pick = show_radial_menu(user, parent, items, custom_check = CALLBACK(src, PROC_REF(check_reskin_menu), user), radius = 38, require_near = TRUE) + if(!pick || !items[pick]) + return + + set_skin_by_name(pick) + to_chat(user, span_info("[parent] is now skinned as '[pick].'")) + + if(!infinite_reskin) + qdel(src) + +/** + * Checks if we are allowed to interact with a radial menu for reskins + * + * Arguments: + * * user The mob interacting with the menu + */ +/datum/component/reskinable_item/proc/check_reskin_menu(mob/user) + if(QDELETED(parent)) + return FALSE + if(user.incapacitated) + return FALSE + return TRUE diff --git a/code/game/machinery/computer/orders/order_items/mining/order_mining.dm b/code/game/machinery/computer/orders/order_items/mining/order_mining.dm index 41c52d9b0a0..f9247d039ce 100644 --- a/code/game/machinery/computer/orders/order_items/mining/order_mining.dm +++ b/code/game/machinery/computer/orders/order_items/mining/order_mining.dm @@ -38,10 +38,6 @@ purchase_path = /obj/item/clothing/gloves/fingerless/punch_mitts cost_per_order = 1000 -/datum/orderable_item/mining/crusher_retool_kit - purchase_path = /obj/item/crusher_trophy/retool_kit - cost_per_order = 150 - /datum/orderable_item/mining/resonator purchase_path = /obj/item/resonator cost_per_order = 710 diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm index ab14767479f..1ad319486c8 100644 --- a/code/game/machinery/dna_scanner.dm +++ b/code/game/machinery/dna_scanner.dm @@ -159,6 +159,41 @@ SIGNAL_HANDLER set_linked_console(null) +// Disk skins +/datum/atom_skin/dna_disk + abstract_type = /datum/atom_skin/dna_disk + +/datum/atom_skin/dna_disk/red + preview_name = "Red" + new_icon_state = "datadisk0" + +/datum/atom_skin/dna_disk/dark_blue + preview_name = "Dark Blue" + new_icon_state = "datadisk1" + +/datum/atom_skin/dna_disk/yellow + preview_name = "Yellow" + new_icon_state = "datadisk2" + +/datum/atom_skin/dna_disk/black + preview_name = "Black" + new_icon_state = "datadisk3" + +/datum/atom_skin/dna_disk/green + preview_name = "Green" + new_icon_state = "datadisk4" + +/datum/atom_skin/dna_disk/purple + preview_name = "Purple" + new_icon_state = "datadisk5" + +/datum/atom_skin/dna_disk/grey + preview_name = "Grey" + new_icon_state = "datadisk6" + +/datum/atom_skin/dna_disk/light_blue + preview_name = "Light Blue" + new_icon_state = "datadisk7" //Just for transferring between genetics machines. /obj/item/disk/data @@ -168,20 +203,10 @@ var/list/mutations = list() var/max_mutations = 6 var/read_only = FALSE //Well,it's still a floppy disk - obj_flags = parent_type::obj_flags | INFINITE_RESKIN - unique_reskin = list( - "Red" = "datadisk0", - "Dark Blue" = "datadisk1", - "Yellow" = "datadisk2", - "Black" = "datadisk3", - "Green" = "datadisk4", - "Purple" = "datadisk5", - "Grey" = "datadisk6", - "Light Blue" = "datadisk7", - ) /obj/item/disk/data/Initialize(mapload) . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/dna_disk, infinite = TRUE) icon_state = "datadisk[rand(0,7)]" add_overlay("datadisk_gene") if(length(genetic_makeup_buffer)) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 508b1b67880..ac2928f5ac1 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -240,14 +240,6 @@ /// A lazylist used for applying fantasy values, contains the actual modification applied to a variable. var/list/fantasy_modifications = null - /// Has the item been reskinned? - var/current_skin - /// List of options to reskin. - var/list/unique_reskin - /// If reskins change base icon state as well - var/unique_reskin_changes_base_icon_state = FALSE - /// If reskins change inhands as well - var/unique_reskin_changes_inhand = FALSE /// Do we apply a click cooldown when resisting this object if it is restraining them? var/resist_cooldown = CLICK_CD_BREAKOUT @@ -283,9 +275,6 @@ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_ITEM, src) - setup_reskinning() - - /obj/item/Destroy(force) // This var exists as a weird proxy "owner" ref // It's used in a few places. Stop using it, and optimially replace all uses please @@ -300,19 +289,6 @@ return ..() - -/obj/item/add_context(atom/source, list/context, obj/item/held_item, mob/user) - . = ..() - - if(!unique_reskin) - return - - if(current_skin && !(obj_flags & INFINITE_RESKIN)) - return - - context[SCREENTIP_CONTEXT_ALT_LMB] = "Reskin" - return CONTEXTUAL_SCREENTIP_SET - /obj/item/click_ctrl(mob/user) SHOULD_NOT_OVERRIDE(TRUE) @@ -458,9 +434,6 @@ else if (siemens_coefficient <= 0.5) .["partially insulated"] = "It is made from a poor insulator that will dampen (but not fully block) electric shocks passing through it." - if(LAZYLEN(unique_reskin) && !current_skin) - .["reskinnable"] = "This item is able to be reskinned! Alt-Click to do so!" - /obj/item/examine_descriptor(mob/user) return "item" diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm index ba75f0b7a3e..b7aa2f91eee 100644 --- a/code/game/objects/items/melee/baton.dm +++ b/code/game/objects/items/melee/baton.dm @@ -764,6 +764,20 @@ /obj/item/melee/baton/security/loaded/hos preload_cell_type = /obj/item/stock_parts/power_store/cell/super +// Stunsword Skins +/datum/atom_skin/stunsword + abstract_type = /datum/atom_skin/stunsword + change_inhand_icon_state = TRUE + change_base_icon_state = TRUE + +/datum/atom_skin/stunsword/default + preview_name = "Default" + new_icon_state = "stunsword" + +/datum/atom_skin/stunsword/energy + preview_name = "Energy" + new_icon_state = "stunsword_energy" + ///Stun Sword /obj/item/melee/baton/security/stunsword name = "\improper NT-20 'Excalibur' Stunsword" @@ -789,14 +803,12 @@ convertible = FALSE obj_flags = UNIQUE_RENAME - unique_reskin = list( - "Default" = "stunsword", - "Energy Stunsword" = "stunsword_energy", - ) - unique_reskin_changes_inhand = TRUE - unique_reskin_changes_base_icon_state = TRUE -/obj/item/melee/baton/security/stunsword/loaded/add_deep_lore() +/obj/item/melee/baton/security/stunsword/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/stunsword) + +/obj/item/melee/baton/security/stunsword/add_deep_lore() return /obj/item/melee/baton/security/stunsword/loaded diff --git a/code/game/objects/items_reskin.dm b/code/game/objects/items_reskin.dm deleted file mode 100644 index 98a3165b5e2..00000000000 --- a/code/game/objects/items_reskin.dm +++ /dev/null @@ -1,95 +0,0 @@ -/// Called when alt clicked and the item has unique reskin options -/obj/item/proc/on_click_alt_reskin(datum/source, mob/user) - SIGNAL_HANDLER - - if(!user.can_perform_action(src, NEED_DEXTERITY)) - return NONE - - if(!(obj_flags & INFINITE_RESKIN) && current_skin) - return NONE - - INVOKE_ASYNC(src, PROC_REF(reskin_obj), user) - return CLICK_ACTION_SUCCESS - -/** - * Checks if we should set up reskinning, - * by default if unique_reskin is set. - * - * Called on setup_reskinning(). - * Inheritors should override this to add their own checks. - */ -/obj/item/proc/check_setup_reskinning() - SHOULD_CALL_PARENT(TRUE) - if(unique_reskin) - return TRUE - - return FALSE - -/** - * Registers signals and context for reskinning, - * if check_setup_reskinning() passes. - * - * Called on Initialize(...). - * Inheritors should override this to add their own setup steps, - * or to avoid double calling register_context(). - */ -/obj/item/proc/setup_reskinning() - SHOULD_CALL_PARENT(FALSE) - if(!check_setup_reskinning()) - return - - RegisterSignal(src, COMSIG_CLICK_ALT, PROC_REF(on_click_alt_reskin)) - register_context() - -/** - * Reskins object based on a user's choice - * - * Arguments: - * * user The mob choosing a reskin option - */ -/obj/item/proc/reskin_obj(mob/user) - if(!LAZYLEN(unique_reskin)) - return - - var/list/items = list() - for(var/reskin_option in unique_reskin) - var/image/item_image = image(icon = src.icon, icon_state = unique_reskin[reskin_option]) - items += list("[reskin_option]" = item_image) - sort_list(items) - - var/pick = show_radial_menu(user, src, items, custom_check = CALLBACK(src, PROC_REF(check_reskin_menu), user), radius = 38, require_near = TRUE) - if(!pick) - return - if(!unique_reskin[pick]) - return - current_skin = pick - icon_state = unique_reskin[pick] - - if (unique_reskin_changes_base_icon_state) - base_icon_state = icon_state - - if (unique_reskin_changes_inhand) - inhand_icon_state = icon_state - - update_appearance() - - to_chat(user, "[src] is now skinned as '[pick].'") - SEND_SIGNAL(src, COMSIG_OBJ_RESKIN, user, pick) - - -/** - * Checks if we are allowed to interact with a radial menu for reskins - * - * Arguments: - * * user The mob interacting with the menu - */ -/obj/item/proc/check_reskin_menu(mob/user) - if(QDELETED(src)) - return FALSE - if(!(obj_flags & INFINITE_RESKIN) && current_skin) - return FALSE - if(!istype(user)) - return FALSE - if(user.incapacitated) - return FALSE - return TRUE diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index 8682dc4a927..672b2eeef1c 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -9,7 +9,6 @@ strip_delay = 4 SECONDS equip_delay_other = 4 SECONDS visor_vars_to_toggle = NONE - unique_reskin_changes_base_icon_state = TRUE var/adjusted_flags = null ///Did we install a filtering cloth? diff --git a/code/modules/clothing/masks/costume.dm b/code/modules/clothing/masks/costume.dm index 8ddfcf5520f..253380adc49 100644 --- a/code/modules/clothing/masks/costume.dm +++ b/code/modules/clothing/masks/costume.dm @@ -1,3 +1,28 @@ +// Mask skins +/datum/atom_skin/joy_mask + abstract_type = /datum/atom_skin/joy_mask + change_base_icon_state = TRUE + +/datum/atom_skin/joy_mask/joy + preview_name = "Joy" + new_icon_state = "joy" + +/datum/atom_skin/joy_mask/flushed + preview_name = "Flushed" + new_icon_state = "flushed" + +/datum/atom_skin/joy_mask/pensive + preview_name = "Pensive" + new_icon_state = "pensive" + +/datum/atom_skin/joy_mask/angry + preview_name = "Angry" + new_icon_state = "angry" + +/datum/atom_skin/joy_mask/pleading + preview_name = "Pleading" + new_icon_state = "pleading" + /obj/item/clothing/mask/joy name = "emotion mask" desc = "Express your happiness or hide your sorrows with this cultured cutout." @@ -5,19 +30,10 @@ base_icon_state = "joy" clothing_flags = MASKINTERNALS flags_inv = HIDESNOUT - obj_flags = parent_type::obj_flags | INFINITE_RESKIN - unique_reskin = list( - "Joy" = "joy", - "Flushed" = "flushed", - "Pensive" = "pensive", - "Angry" = "angry", - "Pleading" = "pleading" - ) - -/obj/item/clothing/mask/joy/reskin_obj(mob/user) +/obj/item/clothing/mask/joy/Initialize(mapload) . = ..() - user.update_worn_mask() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/joy_mask, infinite = TRUE) /obj/item/clothing/mask/mummy name = "mummy mask" diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index dfff6b566f1..f437e1378ac 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -73,13 +73,6 @@ . = ..() dump_attachments() -/obj/item/clothing/under/setup_reskinning() - if(!check_setup_reskinning()) - return - - // We already register context in Initialize. - RegisterSignal(src, COMSIG_CLICK_ALT, PROC_REF(on_click_alt_reskin)) - /obj/item/clothing/under/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) . = ..() diff --git a/code/modules/clothing/under/accessories/_accessories.dm b/code/modules/clothing/under/accessories/_accessories.dm index 0e4e2f98190..993771e4888 100644 --- a/code/modules/clothing/under/accessories/_accessories.dm +++ b/code/modules/clothing/under/accessories/_accessories.dm @@ -31,13 +31,6 @@ . = ..() register_context() -/obj/item/clothing/accessory/setup_reskinning() - if(!check_setup_reskinning()) - return - - // We already register context regardless in Initialize. - RegisterSignal(src, COMSIG_CLICK_ALT, PROC_REF(on_click_alt_reskin)) - /** * Can we be attached to the passed clothing article? */ diff --git a/code/modules/clothing/under/accessories/badges.dm b/code/modules/clothing/under/accessories/badges.dm index 9d7d87a0846..9f871c71015 100644 --- a/code/modules/clothing/under/accessories/badges.dm +++ b/code/modules/clothing/under/accessories/badges.dm @@ -177,28 +177,51 @@ name = "Pre-Approved Cyborg Candidate dogtag" display = "This employee has been screened for negative mental traits to an acceptable level of accuracy, and is approved for the NT Cyborg program as an alternative to medical resuscitation." +// Pride pin skins +/datum/atom_skin/pride_pin + abstract_type = /datum/atom_skin/pride_pin + +/datum/atom_skin/pride_pin/gay + preview_name = "Rainbow Pride" + new_icon_state = "pride" + +/datum/atom_skin/pride_pin/bi + preview_name = "Bisexual Pride" + new_icon_state = "pride_bi" + +/datum/atom_skin/pride_pin/pan + preview_name = "Pansexual Pride" + new_icon_state = "pride_pan" + +/datum/atom_skin/pride_pin/ace + preview_name = "Asexual Pride" + new_icon_state = "pride_ace" + +/datum/atom_skin/pride_pin/enby + preview_name = "Non-binary Pride" + new_icon_state = "pride_enby" + +/datum/atom_skin/pride_pin/trans + preview_name = "Transgender Pride" + new_icon_state = "pride_trans" + +/datum/atom_skin/pride_pin/intersex + preview_name = "Intersex Pride" + new_icon_state = "pride_intersex" + +/datum/atom_skin/pride_pin/lesbian + preview_name = "Lesbian Pride" + new_icon_state = "pride_lesbian" + /obj/item/clothing/accessory/pride name = "pride pin" desc = "A Nanotrasen Diversity & Inclusion Center-sponsored holographic pin to show off your pride, reminding the crew of their unwavering commitment to equity, diversity, and inclusion!" icon_state = "pride" - obj_flags = UNIQUE_RENAME | INFINITE_RESKIN - unique_reskin = list( - "Rainbow Pride" = "pride", - "Bisexual Pride" = "pride_bi", - "Pansexual Pride" = "pride_pan", - "Asexual Pride" = "pride_ace", - "Non-binary Pride" = "pride_enby", - "Transgender Pride" = "pride_trans", - "Intersex Pride" = "pride_intersex", - "Lesbian Pride" = "pride_lesbian", - ) + obj_flags = UNIQUE_RENAME -/obj/item/clothing/accessory/pride/setup_reskinning() - if(!check_setup_reskinning()) - return - - // We already register context regardless in Initialize. - RegisterSignal(src, COMSIG_CLICK_ALT, PROC_REF(on_click_alt_reskin)) +/obj/item/clothing/accessory/pride/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/pride_pin, infinite = TRUE) /obj/item/clothing/accessory/deaf_pin name = "deaf personnel pin" diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm index fa7390b794b..a3bd9c01f4e 100644 --- a/code/modules/clothing/under/costume.dm +++ b/code/modules/clothing/under/costume.dm @@ -275,6 +275,26 @@ can_adjust = FALSE resistance_flags = NONE +// Mech suit skins +/datum/atom_skin/mech_suit + abstract_type = /datum/atom_skin/mech_suit + +/datum/atom_skin/mech_suit/red + preview_name = "Red" + new_icon_state = "red_mech_suit" + +/datum/atom_skin/mech_suit/white + preview_name = "White" + new_icon_state = "white_mech_suit" + +/datum/atom_skin/mech_suit/blue + preview_name = "Blue" + new_icon_state = "blue_mech_suit" + +/datum/atom_skin/mech_suit/black + preview_name = "Black" + new_icon_state = "black_mech_suit" + /obj/item/clothing/under/costume/mech_suit name = "mech pilot's suit" desc = "A mech pilot's suit. Might make your butt look big." @@ -286,13 +306,9 @@ alternate_worn_layer = GLOVES_LAYER //covers hands but gloves can go over it. This is how these things work in my head. can_adjust = FALSE - unique_reskin = list( - "Red" = "red_mech_suit", - "White" = "white_mech_suit", - "Blue" = "blue_mech_suit", - "Black" = "black_mech_suit", - ) - +/obj/item/clothing/under/costume/mech_suit/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/mech_suit) /obj/item/clothing/under/costume/russian_officer name = "\improper Russian officer's uniform" diff --git a/code/modules/loadout/categories/accessories.dm b/code/modules/loadout/categories/accessories.dm index 2c90385b113..7f6a5658aa9 100644 --- a/code/modules/loadout/categories/accessories.dm +++ b/code/modules/loadout/categories/accessories.dm @@ -82,4 +82,4 @@ /datum/loadout_item/accessory/pride name = "Pride Pin" item_path = /obj/item/clothing/accessory/pride - loadout_flags = LOADOUT_FLAG_ALLOW_RESKIN + reskin_datum = /datum/atom_skin/pride_pin diff --git a/code/modules/loadout/loadout_items.dm b/code/modules/loadout/loadout_items.dm index 78561a9401c..a401b426078 100644 --- a/code/modules/loadout/loadout_items.dm +++ b/code/modules/loadout/loadout_items.dm @@ -50,8 +50,9 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) /// Icon state for the UI to use for preview icons. /// Set automatically if null var/ui_icon_state - /// Reskin options of this item if it can be reskinned. - VAR_FINAL/list/cached_reskin_options + /// Base typepath to what reskin datum this item can use to reskin into + /// Doesn't verify that the item_path actually has these reskins + var/reskin_datum /// A list of greyscale colors that are used for items that have greyscale support, but don't allow full customization. /// This is an assoc list of /datum/job_department -> colors, or /datum/job -> colors, allowing for preset colors based on player chosen job. /// Jobs are prioritized over departments. @@ -81,15 +82,6 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) ui_icon = item_path::icon_preview || item_path::icon ui_icon_state = item_path::icon_state_preview || item_path::icon_state - if(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN) - var/obj/item/dummy_item = new item_path() - if(!length(dummy_item.unique_reskin)) - loadout_flags &= ~LOADOUT_FLAG_ALLOW_RESKIN - stack_trace("Loadout item [item_path] has LOADOUT_FLAG_ALLOW_RESKIN but has no unique reskins.") - else - cached_reskin_options = dummy_item.unique_reskin.Copy() - qdel(dummy_item) - /datum/loadout_item/Destroy(force, ...) if(!force) stack_trace("QDEL called on loadout item [type]. This shouldn't ever happen. (Use FORCE if necessary.)") @@ -126,7 +118,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) return set_name(manager, user) if("set_skin") - if(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN) + if(reskin_datum) return set_skin(manager, user, params) return TRUE @@ -205,10 +197,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) /// Used for reskinning an item to an alt skin. /datum/loadout_item/proc/set_skin(datum/preference_middleware/loadout/manager, mob/user, params) - var/reskin_to = params["skin"] - if(!cached_reskin_options[reskin_to]) - return FALSE - + var/reskin_to = params["skin"] // sanity checking isn't necessary because it's all checked when equipped anyways var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) if(!loadout?[item_path]) return FALSE @@ -283,11 +272,13 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) equipped_item.name = trim(item_details[INFO_NAMED], PREVENT_CHARACTER_TRIM_LOSS(MAX_NAME_LEN)) ADD_TRAIT(equipped_item, TRAIT_WAS_RENAMED, "Loadout") - if((loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN) && item_details?[INFO_RESKIN]) + if(reskin_datum && item_details?[INFO_RESKIN]) var/skin_chosen = item_details[INFO_RESKIN] - if(skin_chosen in equipped_item.unique_reskin) - equipped_item.current_skin = skin_chosen - equipped_item.icon_state = equipped_item.unique_reskin[skin_chosen] + for(var/datum/atom_skin/skin_path as anything in valid_subtypesof(reskin_datum)) + if(skin_path::preview_name != skin_chosen) + continue + var/datum/atom_skin/skin_instance = GLOB.atom_skins[skin_path] + skin_instance.apply(equipped_item) if(istype(equipped_item, /obj/item/clothing/accessory)) // Snowflake handing for accessories, because we need to update the thing it's attached to instead if(isclothing(equipped_item.loc)) @@ -296,6 +287,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) update_flag |= (ITEM_SLOT_OCLOTHING|ITEM_SLOT_ICLOTHING) else update_flag |= equipped_item.slot_flags + break return update_flag @@ -336,7 +328,7 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) if((loadout_flags & LOADOUT_FLAG_GREYSCALING_ALLOWED) && !(loadout_flags & LOADOUT_FLAG_JOB_GREYSCALING)) displayed_text[FA_ICON_PALETTE] = "Recolorable" - if(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN) + if(reskin_datum) displayed_text[FA_ICON_SWATCHBOOK] = "Reskinnable" return displayed_text @@ -381,16 +373,17 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories()) * Returns a list of options this item can be reskinned into. */ /datum/loadout_item/proc/get_reskin_options() as /list - if(!(loadout_flags & LOADOUT_FLAG_ALLOW_RESKIN)) + if(!reskin_datum) return null var/list/reskins = list() - for(var/skin in cached_reskin_options) + for(var/datum/atom_skin/skin as anything in valid_subtypesof(reskin_datum)) UNTYPED_LIST_ADD(reskins, list( - "name" = skin, - "tooltip" = skin, - "skin_icon_state" = cached_reskin_options[skin], + "name" = skin::new_name || skin::preview_name, + "tooltip" = skin::preview_name, + "skin_icon" = skin::new_icon, + "skin_icon_state" = skin::new_icon_state, )) return reskins diff --git a/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm index baf71f5cbe3..55533341d89 100644 --- a/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm @@ -12,6 +12,7 @@ suicidal miners against local fauna." icon = 'icons/obj/mining.dmi' icon_state = "crusher" + base_icon_state = "crusher" inhand_icon_state = "crusher0" icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' @@ -56,25 +57,43 @@ var/detonation_damage = 50 /// Damage that the mark additionally does when hit by the crusher via backstab var/backstab_bonus = 30 - /// Used by retool kits when changing the crusher's appearance - var/current_inhand_icon_state = "crusher" /// The file in which our projectile icon resides var/projectile_icon = 'icons/obj/weapons/guns/projectiles.dmi' /// Used by retool kits when changing the crusher's projectile sprite var/projectile_icon_state = "pulse1" /// Wielded damage we deal, aka our "real" damage var/force_wielded = 20 + /// Set to TRUE if the last projectile fired was point-blank at a living target + var/last_projectile_pb = FALSE /obj/item/kinetic_crusher/Initialize(mapload) . = ..() - AddComponent(/datum/component/butchering, \ + AddComponent( \ + /datum/component/butchering, \ speed = 6 SECONDS, \ effectiveness = 110, \ ) + update_reskin(null) //technically it's huge and bulky, but this provides an incentive to use it update_wielding() register_context() +/** + * Adds or updates the reskinning component on the crusher. + * + * * default_skin_typepath: The typepath of skin to apply by default. + * Passing null will either not apply a skin or will reset it to default if one is already applied. + * If a supplied skin is blacklisted, it will be un-blacklisted. + */ +/obj/item/kinetic_crusher/proc/update_reskin(datum/atom_skin/crusher_skin/default_skin_typepath) + AddComponent( \ + /datum/component/reskinable_item, \ + /datum/atom_skin/crusher_skin, \ + infinite = TRUE, \ + initial_skin = default_skin_typepath ? default_skin_typepath::preview_name : null, \ + blacklisted_subtypes = subtypesof(/datum/atom_skin/crusher_skin/locked) - default_skin_typepath, \ + ) + /obj/item/kinetic_crusher/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() if(!held_item) @@ -236,6 +255,7 @@ var/turf/proj_turf = user.loc if(!isturf(proj_turf)) return + last_projectile_pb = get_dist(target, user) <= 1 && isliving(target) var/obj/projectile/destabilizer/destabilizer = new(proj_turf) SEND_SIGNAL(src, COMSIG_CRUSHER_FIRED_BLAST, target, user, destabilizer) destabilizer.icon = projectile_icon @@ -281,7 +301,7 @@ return TRUE /obj/item/kinetic_crusher/update_icon_state() - inhand_icon_state = "[current_inhand_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]" // this is not icon_state and not supported by 2hcomponent + inhand_icon_state = "[base_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]" // this is not icon_state and not supported by 2hcomponent return ..() /obj/item/kinetic_crusher/update_overlays() diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm index c4ff738b9cb..dca037e37cd 100644 --- a/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm +++ b/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm @@ -1,164 +1,95 @@ -/*! - * Contains crusher trophies that are not obtained from fauna - */ -/// Cosmetic items for changing the crusher's look -/obj/item/crusher_trophy/retool_kit - name = "crusher retool kit" - desc = "A toolkit for changing the crusher's appearance without affecting the device's function." - icon = 'icons/obj/mining.dmi' - icon_state = "retool_kit" - denied_type = /obj/item/crusher_trophy/retool_kit - - /// Currently picked crusher reskin - var/datum/crusher_skin/active_skin = /datum/crusher_skin/sword - /// If this kit forces some specific skin, or can pick between subtypes - var/forced_skin - -/obj/item/crusher_trophy/retool_kit/Destroy(force) - if (istype(active_skin)) - QDEL_NULL(active_skin) - return ..() - -/obj/item/crusher_trophy/retool_kit/effect_desc() - return "the crusher to have the appearance of \a [active_skin::name]" - -/obj/item/crusher_trophy/retool_kit/add_to(obj/item/kinetic_crusher/pkc, mob/user) - if (!forced_skin) - var/list/choices = list() - for (var/datum/crusher_skin/skin as anything in subtypesof(/datum/crusher_skin)) - if (skin::normal_skin) - choices[skin] = icon(skin::retool_icon || 'icons/obj/mining.dmi', skin::retool_icon_state) - var/datum/crusher_skin/chosen_skin = show_radial_menu(user, src, choices, tooltips = TRUE, require_near = TRUE) - if (!chosen_skin) - return - active_skin = chosen_skin - else - active_skin = forced_skin - . = ..() - if(!.) - return - active_skin = new active_skin(pkc) - if (active_skin.retool_icon) - pkc.icon = active_skin.retool_icon - pkc.icon_state = active_skin.retool_icon_state - pkc.current_inhand_icon_state = active_skin.retool_inhand_icon - if (active_skin.retool_projectile_icon) - pkc.projectile_icon = active_skin.retool_projectile_icon - if (active_skin.retool_projectile_icon_state) - pkc.projectile_icon_state = active_skin.retool_projectile_icon_state - // Should either have both, or neither - if (active_skin.retool_lefthand_file) - pkc.lefthand_file = active_skin.retool_lefthand_file - pkc.righthand_file = active_skin.retool_righthand_file - if(active_skin.retool_worn_file) - pkc.worn_icon = active_skin.retool_worn_file - pkc.worn_icon_state = active_skin::retool_icon_state - if (active_skin.retool_inhand_x) - pkc.inhand_x_dimension = active_skin.retool_inhand_x - if (active_skin.retool_inhand_y) - pkc.inhand_y_dimension = active_skin.retool_inhand_y - pkc.update_appearance() - pkc.update_slot_icon() - -/obj/item/crusher_trophy/retool_kit/remove_from(obj/item/kinetic_crusher/pkc) - var/skin_type = active_skin.type - qdel(active_skin) - active_skin = skin_type - pkc.icon = initial(pkc.icon) - pkc.icon_state = initial(pkc.icon_state) - pkc.current_inhand_icon_state = initial(pkc.current_inhand_icon_state) - pkc.projectile_icon = initial(pkc.projectile_icon) - pkc.projectile_icon_state = initial(pkc.projectile_icon_state) - pkc.lefthand_file = initial(pkc.lefthand_file) - pkc.righthand_file = initial(pkc.righthand_file) - pkc.worn_icon = initial(pkc.worn_icon) - pkc.worn_icon_state = initial(pkc.worn_icon_state) - pkc.inhand_x_dimension = initial(pkc.inhand_x_dimension) - pkc.inhand_y_dimension = initial(pkc.inhand_y_dimension) - pkc.update_appearance() - pkc.update_slot_icon() - return ..() - -/// Alternate PKC skins -/datum/crusher_skin - /// Name of the modification - var/name = "error that should be reported to coders" - /// Specifies the icon file in which the crusher's new state is stored. - var/retool_icon = 'icons/obj/mining.dmi' - ///Specifies the sprite/icon state which the crusher is changed to as an item. Should appear in the icons/obj/mining.dmi file with accompanying "lit" and "recharging" sprites - var/retool_icon_state = "ipickaxe" - ///Specifies the icon state for the crusher's appearance in hand. Should appear in both retool_lefthand_file and retool_righthand_file. - var/retool_inhand_icon = "ipickaxe" +// Alternate PKC skins +/datum/atom_skin/crusher_skin + abstract_type = /datum/atom_skin/crusher_skin + change_base_icon_state = TRUE + new_icon = 'icons/obj/mining.dmi' + new_icon_state = "ipickaxe" + /// Specifies the icon state for the crusher's appearance in hand. Should appear in both new_lefthand_file and new_righthand_file. + var/new_inhand_icon = "ipickaxe" /// Specifies the icon file in which the crusher's projectile sprite is located. - var/retool_projectile_icon = 'icons/obj/weapons/guns/projectiles.dmi' - ///For if the retool kit changes the projectile's appearance. - var/retool_projectile_icon_state = null + var/new_projectile_icon = 'icons/obj/weapons/guns/projectiles.dmi' + /// For if the retool kit changes the projectile's appearance. + var/new_projectile_icon_state /// Specifies the left hand inhand icon file. Don't forget to set the right hand file as well. - var/retool_lefthand_file = null + var/new_lefthand_file /// Specifies the right hand inhand icon file. Don't forget to set the left hand file as well. - var/retool_righthand_file = null + var/new_righthand_file /// Specifies the worn icon file. - var/retool_worn_file = null + var/new_worn_file /// Specifies the X dimensions of the new inhand, only relevant with different inhand files. - var/retool_inhand_x = null + var/new_inhandx /// Specifies the Y dimensions of the new inhand, only relevant with different inhand files. - var/retool_inhand_y = null - /// Can this skin be normally selected by a generic retool kit? - var/normal_skin = TRUE - /// Crusher this skin is attached to - var/obj/item/kinetic_crusher/crusher + var/new_inhandy -/datum/crusher_skin/New(obj/item/kinetic_crusher/new_crusher) +/datum/atom_skin/crusher_skin/apply(obj/item/kinetic_crusher/apply_to) . = ..() - crusher = new_crusher + APPLY_VAR_OR_RESET_INITIAL(apply_to, inhand_icon_state, new_inhand_icon, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, projectile_icon, new_projectile_icon, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, projectile_icon_state, new_projectile_icon_state, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, lefthand_file, new_lefthand_file, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, righthand_file, new_righthand_file, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, worn_icon, new_worn_file, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, worn_icon_state, new_icon_state, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, inhand_x_dimension, new_inhandx, reset_missing) + APPLY_VAR_OR_RESET_INITIAL(apply_to, inhand_y_dimension, new_inhandy, reset_missing) -/datum/crusher_skin/Destroy(force) - crusher = null - return ..() - -/datum/crusher_skin/sword - name = "sword" - retool_icon_state = "crusher_sword" - retool_inhand_icon = "crusher_sword" - -/datum/crusher_skin/harpoon - name = "harpoon" - retool_icon_state = "crusher_harpoon" - retool_inhand_icon = "crusher_harpoon" - retool_projectile_icon_state = "pulse_harpoon" - -/datum/crusher_skin/harpoon/New(obj/item/kinetic_crusher/new_crusher) +/datum/atom_skin/crusher_skin/clear_skin(obj/item/kinetic_crusher/clear_from) . = ..() - RegisterSignal(crusher, COMSIG_ITEM_ATTACK_ANIMATION, PROC_REF(on_attack_animation)) + RESET_INITIAL_IF_SET(clear_from, inhand_icon_state, new_inhand_icon) + RESET_INITIAL_IF_SET(clear_from, projectile_icon, new_projectile_icon) + RESET_INITIAL_IF_SET(clear_from, projectile_icon_state, new_projectile_icon_state) + RESET_INITIAL_IF_SET(clear_from, lefthand_file, new_lefthand_file) + RESET_INITIAL_IF_SET(clear_from, righthand_file, new_righthand_file) + RESET_INITIAL_IF_SET(clear_from, worn_icon, new_worn_file) + RESET_INITIAL_IF_SET(clear_from, worn_icon_state, new_icon_state) + RESET_INITIAL_IF_SET(clear_from, inhand_x_dimension, new_inhandx) + RESET_INITIAL_IF_SET(clear_from, inhand_y_dimension, new_inhandy) -/datum/crusher_skin/harpoon/Destroy(force) - UnregisterSignal(crusher, COMSIG_ITEM_ATTACK_ANIMATION) - return ..() +/datum/atom_skin/crusher_skin/sword + new_name = "proto-kinetic sword" + preview_name = "Sword" + new_icon_state = "crusher_sword" + new_inhand_icon = "crusher_sword" -/datum/crusher_skin/harpoon/proc/on_attack_animation(obj/item/source, atom/movable/attacker, atom/attacked_atom, animation_type, list/image_override, list/animation_override) +/datum/atom_skin/crusher_skin/harpoon + new_name = "proto-kinetic harpoon" + preview_name = "Harpoon" + new_icon_state = "crusher_harpoon" + new_inhand_icon = "crusher_harpoon" + new_projectile_icon_state = "pulse_harpoon" + +/datum/atom_skin/crusher_skin/harpoon/apply(atom/apply_to) + . = ..() + RegisterSignal(apply_to, COMSIG_ITEM_ATTACK_ANIMATION, PROC_REF(on_attack_animation)) + +/datum/atom_skin/crusher_skin/harpoon/clear_skin(atom/clear_from) + . = ..() + UnregisterSignal(clear_from, COMSIG_ITEM_ATTACK_ANIMATION) + +/datum/atom_skin/crusher_skin/harpoon/proc/on_attack_animation(obj/item/source, atom/movable/attacker, atom/attacked_atom, animation_type, list/image_override, list/animation_override) SIGNAL_HANDLER // If nothing is forcing an animation type, attack with a piercing animation because we're a harpoon if (!animation_type) animation_override += ATTACK_ANIMATION_PIERCE -/datum/crusher_skin/dagger - name = "dual dagger and blaster" - retool_icon_state = "crusher_dagger" - retool_inhand_icon = "crusher_dagger" - /// Are we doing a blaster animation right now? - var/blaster_strike = FALSE +/datum/atom_skin/crusher_skin/dagger + new_name = "proto-kinetic dual dagger and blaster" + preview_name = "Dagger and Blaster" + new_icon_state = "crusher_dagger" + new_inhand_icon = "crusher_dagger" -/datum/crusher_skin/dagger/New(obj/item/kinetic_crusher/new_crusher) +/datum/atom_skin/crusher_skin/dagger/apply(atom/apply_to) . = ..() - RegisterSignal(crusher, COMSIG_ITEM_ATTACK_ANIMATION, PROC_REF(on_attack_animation)) - RegisterSignal(crusher, COMSIG_CRUSHER_FIRED_BLAST, PROC_REF(on_fired_blast)) + RegisterSignal(apply_to, COMSIG_ITEM_ATTACK_ANIMATION, PROC_REF(on_attack_animation)) + RegisterSignal(apply_to, COMSIG_CRUSHER_FIRED_BLAST, PROC_REF(on_fired_blast)) -/datum/crusher_skin/dagger/Destroy(force) - UnregisterSignal(crusher, list(COMSIG_ITEM_ATTACK_ANIMATION, COMSIG_CRUSHER_FIRED_BLAST)) - return ..() +/datum/atom_skin/crusher_skin/dagger/clear_skin(atom/clear_from) + . = ..() + UnregisterSignal(clear_from, COMSIG_ITEM_ATTACK_ANIMATION) + UnregisterSignal(clear_from, COMSIG_CRUSHER_FIRED_BLAST) -/datum/crusher_skin/dagger/proc/on_attack_animation(obj/item/kinetic_crusher/source, atom/movable/attacker, atom/attacked_atom, animation_type, list/image_override, list/animation_override, list/angle_override) +/datum/atom_skin/crusher_skin/dagger/proc/on_attack_animation(obj/item/kinetic_crusher/source, atom/movable/attacker, atom/attacked_atom, animation_type, list/image_override, list/animation_override, list/angle_override) SIGNAL_HANDLER // If we've been forcefully assigned an animation type already, we shouldn't do the custom attack animation logic @@ -167,11 +98,11 @@ if (isliving(attacked_atom)) var/mob/living/target = attacked_atom - if (blaster_strike) + if (source.last_projectile_pb) image_override += image(icon = 'icons/obj/mining.dmi', icon_state = "crusher_dagger_blaster") angle_override += 0 animation_override += ATTACK_ANIMATION_PIERCE - blaster_strike = FALSE + source.last_projectile_pb = FALSE return if (target.has_status_effect(/datum/status_effect/crusher_mark)) @@ -179,33 +110,62 @@ image_override += image(icon = 'icons/obj/mining.dmi', icon_state = "crusher_dagger_melee") -/datum/crusher_skin/dagger/proc/on_fired_blast(obj/item/kinetic_crusher/source, atom/target, mob/living/user, obj/projectile/destabilizer/destabilizer) +/datum/atom_skin/crusher_skin/dagger/proc/on_fired_blast(obj/item/kinetic_crusher/source, atom/target, mob/living/user, obj/projectile/destabilizer/destabilizer) SIGNAL_HANDLER if (isliving(target) && get_dist(target, user) <= 1) - blaster_strike = TRUE user.do_item_attack_animation(target, used_item = source) -/datum/crusher_skin/glaive - name = "glaive" - retool_icon_state = "crusher_glaive" - retool_inhand_icon = "crusher_glaive" - retool_lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' - retool_righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' - retool_inhand_x = 64 - retool_inhand_y = 64 +/datum/atom_skin/crusher_skin/glaive + new_name = "proto-kinetic glaive" + preview_name = "Glaive" + new_icon_state = "crusher_glaive" + new_inhand_icon = "crusher_glaive" + new_lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' + new_righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' + new_inhandx = 64 + new_inhandy = 64 + +// Locked skins that cannot be selected normally +/datum/atom_skin/crusher_skin/locked + abstract_type = /datum/atom_skin/crusher_skin/locked + +/datum/atom_skin/crusher_skin/locked/ashen_skull + preview_name = "Skull" + new_icon_state = "crusher_skull" + new_inhand_icon = "crusher_skull" + new_projectile_icon_state = "pulse_skull" + +/// Unlockable (or forced) skins +/obj/item/crusher_trophy/retool_kit + name = "crusher retool kit" + desc = "A toolkit for changing the crusher's appearance without affecting the device's function." + icon = 'icons/obj/mining.dmi' + icon_state = "retool_kit" + denied_type = /obj/item/crusher_trophy/retool_kit + + /// What skin do we apply when attached + var/datum/atom_skin/crusher_skin/forced_skin + +/obj/item/crusher_trophy/retool_kit/effect_desc() + return "the crusher to have the appearance of \a [forced_skin::preview_name]" + +/obj/item/crusher_trophy/retool_kit/add_to(obj/item/kinetic_crusher/pkc, mob/user) + . = ..() + if(!.) + return + + pkc.update_reskin(forced_skin) + +/obj/item/crusher_trophy/retool_kit/remove_from(obj/item/kinetic_crusher/pkc) + pkc.update_reskin(null) // resets reskin component + return ..() /obj/item/crusher_trophy/retool_kit/ashenskull name = "ashen skull" desc = "It burns with the flame of the necropolis, whispering in your ear. It demands to be bound to a suitable weapon." icon_state = "retool_kit_skull" - forced_skin = /datum/crusher_skin/ashen_skull + forced_skin = /datum/atom_skin/crusher_skin/locked/ashen_skull /obj/item/crusher_trophy/retool_kit/ashenskull/effect_desc() return "the crusher to appear corrupted by infernal powers" - -/datum/crusher_skin/ashen_skull - retool_icon_state = "crusher_skull" - retool_inhand_icon = "crusher_skull" - retool_projectile_icon_state = "pulse_skull" - normal_skin = FALSE diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm index 46c27082c75..2856dddc296 100644 --- a/code/modules/paperwork/clipboard.dm +++ b/code/modules/paperwork/clipboard.dm @@ -1,3 +1,20 @@ +// Clipboard skins +/datum/atom_skin/clipboard + abstract_type = /datum/atom_skin/clipboard + change_inhand_icon_state = TRUE + +/datum/atom_skin/clipboard/brown + preview_name = "Brown" + new_icon_state = "clipboard" + +/datum/atom_skin/clipboard/black + preview_name = "Black" + new_icon_state = "clipboard_black" + +/datum/atom_skin/clipboard/white + preview_name = "White" + new_icon_state = "clipboard_white" + /** * Clipboard */ @@ -14,13 +31,6 @@ slot_flags = ITEM_SLOT_BELT resistance_flags = FLAMMABLE - unique_reskin = list( - "Brown" = "clipboard", - "Black" = "clipboard_black", - "White" = "clipboard_white", - ) - unique_reskin_changes_inhand = TRUE - /// The stored pen var/obj/item/pen/pen /// Is the pen integrated? @@ -39,6 +49,7 @@ /obj/item/clipboard/Initialize(mapload) update_appearance() . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/clipboard) /obj/item/clipboard/Destroy() QDEL_NULL(pen) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 9bd4324a583..e92bfe47b47 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -177,6 +177,38 @@ time = 3 SECONDS category = CAT_TOOLS +// Skins for captain's fountain pen +/datum/atom_skin/cap_pen + abstract_type = /datum/atom_skin/cap_pen + +/datum/atom_skin/cap_pen/apply(atom/apply_to) + . = ..() + apply_to.desc = "It's an expensive [preview_name] fountain pen. The nib is quite sharp." + +/datum/atom_skin/cap_pen/clear_skin(atom/clear_from) + . = ..() + clear_from.desc = initial(clear_from.desc) + +/datum/atom_skin/cap_pen/oak + preview_name = "Oak" + new_icon_state = "pen-fountain-o" + +/datum/atom_skin/cap_pen/gold + preview_name = "Gold" + new_icon_state = "pen-fountain-g" + +/datum/atom_skin/cap_pen/rosewood + preview_name = "Rosewood" + new_icon_state = "pen-fountain-r" + +/datum/atom_skin/cap_pen/black_silver + preview_name = "Black and Silver" + new_icon_state = "pen-fountain-b" + +/datum/atom_skin/cap_pen/command_blue + preview_name = "Command Blue" + new_icon_state = "pen-fountain-cb" + /obj/item/pen/fountain/captain name = "captain's fountain pen" desc = "It's an expensive Oak fountain pen. The nib is quite sharp." @@ -187,22 +219,15 @@ custom_materials = list(/datum/material/gold = SMALL_MATERIAL_AMOUNT*7.5) sharpness = SHARP_EDGED resistance_flags = FIRE_PROOF - unique_reskin = list( - "Oak" = "pen-fountain-o", - "Gold" = "pen-fountain-g", - "Rosewood" = "pen-fountain-r", - "Black and Silver" = "pen-fountain-b", - "Command Blue" = "pen-fountain-cb" - ) embed_type = /datum/embedding/pen/captain dart_insert_casing_icon_state = "overlay_fountainpen_gold" dart_insert_projectile_icon_state = "overlay_fountainpen_gold_proj" var/list/overlay_reskin = list( - "Oak" = "overlay_fountainpen_gold", - "Gold" = "overlay_fountainpen_gold", - "Rosewood" = "overlay_fountainpen_gold", - "Black and Silver" = "overlay_fountainpen", - "Command Blue" = "overlay_fountainpen_gold" + /datum/atom_skin/cap_pen/black_silver::preview_name = "overlay_fountainpen", + /datum/atom_skin/cap_pen/command_blue::preview_name = "overlay_fountainpen_gold", + /datum/atom_skin/cap_pen/gold::preview_name = "overlay_fountainpen_gold", + /datum/atom_skin/cap_pen/oak::preview_name = "overlay_fountainpen_gold", + /datum/atom_skin/cap_pen/rosewood::preview_name = "overlay_fountainpen_gold", ) /datum/embedding/pen/captain @@ -210,24 +235,21 @@ /obj/item/pen/fountain/captain/Initialize(mapload) . = ..() - AddComponent(/datum/component/butchering, \ - speed = 20 SECONDS, \ - effectiveness = 115, \ + AddComponent( \ + /datum/component/butchering, \ + speed = 20 SECONDS, \ + effectiveness = 115, \ ) + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/cap_pen) //the pen is mightier than the sword RegisterSignal(src, COMSIG_DART_INSERT_PARENT_RESKINNED, PROC_REF(reskin_dart_insert)) -/obj/item/pen/fountain/captain/reskin_obj(mob/M) - ..() - if(current_skin) - desc = "It's an expensive [current_skin] fountain pen. The nib is quite sharp." - - -/obj/item/pen/fountain/captain/proc/reskin_dart_insert(datum/component/dart_insert/insert_comp) +/obj/item/pen/fountain/captain/proc/reskin_dart_insert(datum/component/dart_insert/insert_comp, skin) + SIGNAL_HANDLER if(!istype(insert_comp)) //You really shouldn't be sending this signal from anything other than a dart_insert component return - insert_comp.casing_overlay_icon_state = overlay_reskin[current_skin] - insert_comp.projectile_overlay_icon_state = "[overlay_reskin[current_skin]]_proj" + insert_comp.casing_overlay_icon_state = overlay_reskin[skin] + insert_comp.projectile_overlay_icon_state = "[overlay_reskin[skin]]_proj" /obj/item/pen/item_ctrl_click(mob/living/carbon/user) if(loc != user) diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 6bc9022e258..c3a82c96b72 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -9,7 +9,6 @@ pickup_sound = 'sound/items/handling/gun/gun_pick_up.ogg' drop_sound = 'sound/items/handling/gun/gun_drop.ogg' sound_vary = TRUE - unique_reskin_changes_base_icon_state = TRUE ///sound when inserting magazine var/load_sound = 'sound/items/weapons/gun/general/magazine_insert_full.ogg' diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 06a8b50c566..6f4b9be9330 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -97,8 +97,7 @@ . = ..() var/live_ammo = get_ammo(FALSE, FALSE) . += "[live_ammo ? live_ammo : "None"] of those are live rounds." - if (current_skin) - . += span_notice("It can be spun with [EXAMINE_HINT("alt-click")].") + . += span_notice("It can be spun with [EXAMINE_HINT("alt-click")].") /obj/item/gun/ballistic/revolver/ignition_effect(atom/A, mob/user) if(last_fire && last_fire + 15 SECONDS > world.time) @@ -112,6 +111,47 @@ base_icon_state = "c38" fire_sound = 'sound/items/weapons/gun/revolver/shot.ogg' +// 38 special skins +/datum/atom_skin/det_revolver + abstract_type = /datum/atom_skin/det_revolver + change_base_icon_state = TRUE + +/datum/atom_skin/det_revolver/default + preview_name = "Default" + new_icon_state = "c38" + +/datum/atom_skin/det_revolver/fitz_special + preview_name = "Fitz Special" + new_icon_state = "c38_fitz" + +/datum/atom_skin/det_revolver/police_positive_special + preview_name = "Police Positive Special" + new_icon_state = "c38_police" + +/datum/atom_skin/det_revolver/blued_steel + preview_name = "Blued Steel" + new_icon_state = "c38_blued" + +/datum/atom_skin/det_revolver/stainless_steel + preview_name = "Stainless Steel" + new_icon_state = "c38_stainless" + +/datum/atom_skin/det_revolver/gold_trim + preview_name = "Gold Trim" + new_icon_state = "c38_trim" + +/datum/atom_skin/det_revolver/golden + preview_name = "Golden" + new_icon_state = "c38_gold" + +/datum/atom_skin/det_revolver/peacemaker + preview_name = "The Peacemaker" + new_icon_state = "c38_peacemaker" + +/datum/atom_skin/det_revolver/black_panther + preview_name = "Black Panther" + new_icon_state = "c38_panther" + /obj/item/gun/ballistic/revolver/c38/detective name = "\improper Colt Detective Special" desc = "A classic, if not outdated, law enforcement firearm. Uses .38 Special rounds. \nSome spread rumors that if you loosen the barrel with a wrench, you can \"improve\" it." @@ -126,17 +166,10 @@ misfire_percentage_increment = 25 //about 1 in 4 rounds, which increases rapidly every shot obj_flags = UNIQUE_RENAME - unique_reskin = list( - "Default" = "c38", - "Fitz Special" = "c38_fitz", - "Police Positive Special" = "c38_police", - "Blued Steel" = "c38_blued", - "Stainless Steel" = "c38_stainless", - "Gold Trim" = "c38_trim", - "Golden" = "c38_gold", - "The Peacemaker" = "c38_peacemaker", - "Black Panther" = "c38_panther" - ) + +/obj/item/gun/ballistic/revolver/c38/detective/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/det_revolver) /obj/item/gun/ballistic/revolver/badass name = "\improper Badass Revolver" diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index e3b631b0e79..124284f3c99 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -285,6 +285,35 @@ /obj/item/gun/ballistic/shotgun/bulldog/unrestricted pin = /obj/item/firing_pin +// Double barrel shotgun skins +/datum/atom_skin/bar_shotgun + abstract_type = /datum/atom_skin/bar_shotgun + change_base_icon_state = TRUE + +/datum/atom_skin/bar_shotgun/default + preview_name = "Standard Finish" + new_icon_state = "dshotgun" + +/datum/atom_skin/bar_shotgun/dark_red + preview_name = "Dark Red Finish" + new_icon_state = "dshotgun_d" + +/datum/atom_skin/bar_shotgun/ash + preview_name = "Ash" + new_icon_state = "dshotgun_f" + +/datum/atom_skin/bar_shotgun/faded_grey + preview_name = "Faded Grey" + new_icon_state = "dshotgun_g" + +/datum/atom_skin/bar_shotgun/maple + preview_name = "Maple" + new_icon_state = "dshotgun_l" + +/datum/atom_skin/bar_shotgun/rosewood + preview_name = "Rosewood" + new_icon_state = "dshotgun_p" + ///////////////////////////// // DOUBLE BARRELED SHOTGUN // ///////////////////////////// @@ -304,18 +333,15 @@ sawn_desc = "Omar's coming!" obj_flags = UNIQUE_RENAME rack_sound_volume = 0 - unique_reskin = list("Default" = "dshotgun", - "Dark Red Finish" = "dshotgun_d", - "Ash" = "dshotgun_f", - "Faded Grey" = "dshotgun_g", - "Maple" = "dshotgun_l", - "Rosewood" = "dshotgun_p" - ) semi_auto = TRUE bolt_type = BOLT_TYPE_NO_BOLT can_be_sawn_off = TRUE pb_knockback = 3 // it's a super shotgun! +/obj/item/gun/ballistic/shotgun/doublebarrel/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/bar_shotgun) + /obj/item/gun/ballistic/shotgun/doublebarrel/sawoff(mob/user) . = ..() if(.) diff --git a/code/modules/reagents/reagent_containers/medigel.dm b/code/modules/reagents/reagent_containers/medigel.dm index f21c08cdb0c..d88a427277c 100644 --- a/code/modules/reagents/reagent_containers/medigel.dm +++ b/code/modules/reagents/reagent_containers/medigel.dm @@ -1,3 +1,31 @@ +// Gel skins +/datum/atom_skin/med_gel + abstract_type = /datum/atom_skin/med_gel + +/datum/atom_skin/med_gel/blue + preview_name = "Blue" + new_icon_state = "medigel_blue" + +/datum/atom_skin/med_gel/cyan + preview_name = "Cyan" + new_icon_state = "medigel_cyan" + +/datum/atom_skin/med_gel/green + preview_name = "Green" + new_icon_state = "medigel_green" + +/datum/atom_skin/med_gel/red + preview_name = "Red" + new_icon_state = "medigel_red" + +/datum/atom_skin/med_gel/orange + preview_name = "Orange" + new_icon_state = "medigel_orange" + +/datum/atom_skin/med_gel/purple + preview_name = "Purple" + new_icon_state = "medigel_purple" + /obj/item/reagent_containers/medigel name = "medical gel" desc = "A medical gel applicator bottle, designed for precision application, with an unscrewable cap." @@ -23,15 +51,11 @@ var/apply_method = "spray" //the thick gel is sprayed and then dries into patch like film. var/self_delay = 30 custom_price = PAYCHECK_CREW * 2 - unique_reskin = list( - "Blue" = "medigel_blue", - "Cyan" = "medigel_cyan", - "Green" = "medigel_green", - "Red" = "medigel_red", - "Orange" = "medigel_orange", - "Purple" = "medigel_purple" - ) +/obj/item/reagent_containers/medigel/Initialize(mapload) + . = ..() + if(icon_state == "medigel") // oh yeah baby raw icon state check to make sure we can't reskin preset gels + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/med_gel) /obj/item/reagent_containers/medigel/mode_change_message(mob/user) var/squirt_mode = amount_per_transfer_from_this == initial(amount_per_transfer_from_this) @@ -78,21 +102,18 @@ name = "medical gel (libital)" desc = "A medical gel applicator bottle, designed for precision application, with an unscrewable cap. This one contains libital, for treating cuts and bruises. Libital does minor liver damage. Diluted with granibitaluri." icon_state = "brutegel" - current_skin = "brutegel" list_reagents = list(/datum/reagent/medicine/c2/libital = 24, /datum/reagent/medicine/granibitaluri = 36) /obj/item/reagent_containers/medigel/aiuri name = "medical gel (aiuri)" desc = "A medical gel applicator bottle, designed for precision application, with an unscrewable cap. This one contains aiuri, useful for treating burns. Aiuri does minor eye damage. Diluted with granibitaluri." icon_state = "burngel" - current_skin = "burngel" list_reagents = list(/datum/reagent/medicine/c2/aiuri = 24, /datum/reagent/medicine/granibitaluri = 36) /obj/item/reagent_containers/medigel/synthflesh name = "medical gel (synthflesh)" desc = "A medical gel applicator bottle, designed for precision application, with an unscrewable cap. This one contains synthflesh, a slightly toxic medicine capable of healing bruises, burns, and husks." icon_state = "synthgel" - current_skin = "synthgel" list_reagents = list(/datum/reagent/medicine/c2/synthflesh = 60) list_reagents_purity = 1 amount_per_transfer_from_this = 60 @@ -117,6 +138,5 @@ name = "sterilizer gel" desc = "gel bottle loaded with non-toxic sterilizer. Useful in preparation for surgery." icon_state = "medigel_blue" - current_skin = "medigel_blue" list_reagents = list(/datum/reagent/space_cleaner/sterilizine = 60) custom_price = PAYCHECK_CREW * 2 diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 869131dd5e8..4c4f0dbcbf9 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -420,6 +420,23 @@ . = ..() icon_state = pick("sprayer_sus_1", "sprayer_sus_2", "sprayer_sus_3", "sprayer_sus_4", "sprayer_sus_5","sprayer_sus_6", "sprayer_sus_7", "sprayer_sus_8") +// Spray bottle skins +/datum/atom_skin/med_spray + abstract_type = /datum/atom_skin/med_spray + change_inhand_icon_state = TRUE + +/datum/atom_skin/med_spray/red + preview_name = "Red" + new_icon_state = "sprayer_med_red" + +/datum/atom_skin/med_spray/yellow + preview_name = "Yellow" + new_icon_state = "sprayer_med_yellow" + +/datum/atom_skin/med_spray/blue + preview_name = "Blue" + new_icon_state = "sprayer_med_blue" + /obj/item/reagent_containers/spray/medical name = "medical spray bottle" icon = 'icons/obj/medical/chemical.dmi' @@ -428,20 +445,10 @@ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' volume = 100 - unique_reskin = list("Red" = "sprayer_med_red", - "Yellow" = "sprayer_med_yellow", - "Blue" = "sprayer_med_blue") -/obj/item/reagent_containers/spray/medical/reskin_obj(mob/M) - ..() - switch(icon_state) - if("sprayer_med_red") - inhand_icon_state = "sprayer_med_red" - if("sprayer_med_yellow") - inhand_icon_state = "sprayer_med_yellow" - if("sprayer_med_blue") - inhand_icon_state = "sprayer_med_blue" - M.update_held_items() +/obj/item/reagent_containers/spray/medical/Initialize(mapload, vol) + . = ..() + AddComponent(/datum/component/reskinable_item, /datum/atom_skin/med_spray) /obj/item/reagent_containers/spray/hercuri name = "medical spray (hercuri)" diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index e4f6debfe68..f71cb0f33cf 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -266,6 +266,7 @@ #include "recycle_recycling.dm" #include "required_map_items.dm" #include "resist.dm" +#include "reskin_validation.dm" #include "reta_system.dm" #include "say.dm" #include "screenshot_airlocks.dm" diff --git a/code/modules/unit_tests/reskin_validation.dm b/code/modules/unit_tests/reskin_validation.dm new file mode 100644 index 00000000000..85e454058ce --- /dev/null +++ b/code/modules/unit_tests/reskin_validation.dm @@ -0,0 +1,15 @@ +/datum/unit_test/reskin_validation + +/datum/unit_test/reskin_validation/Run() + var/list/known_names = list() + for(var/datum/atom_skin/skin as anything in valid_subtypesof(/datum/atom_skin)) + if(isnull(skin::preview_name)) + TEST_FAIL("Reskin [skin] is missing a preview_name.") + // preview names are bundled by abstract types + else if(known_names["[skin::preview_name]-[skin::abstract_type]"]) + TEST_FAIL("Reskin [skin] has a duplicate preview_name [skin::preview_name].") + else + known_names["[skin::preview_name]-[skin::abstract_type]"] = TRUE + + if(skin::new_icon && skin::new_icon_state && !icon_exists(skin::new_icon, skin::new_icon_state)) + TEST_FAIL("Reskin [skin] has a new_icon_state [skin::new_icon_state] that does not exist in file [skin::new_icon].") diff --git a/tgstation.dme b/tgstation.dme index b0e04c9b7f6..5dbe1562184 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -209,6 +209,7 @@ #include "code\__DEFINES\regex.dm" #include "code\__DEFINES\religion.dm" #include "code\__DEFINES\research.dm" +#include "code\__DEFINES\reskin.dm" #include "code\__DEFINES\resonator.dm" #include "code\__DEFINES\revolution.dm" #include "code\__DEFINES\robots.dm" @@ -1271,6 +1272,7 @@ #include "code\datums\components\regenerator.dm" #include "code\datums\components\religious_tool.dm" #include "code\datums\components\rename.dm" +#include "code\datums\components\reskinnable_atom.dm" #include "code\datums\components\revenge_ability.dm" #include "code\datums\components\rot.dm" #include "code\datums\components\rotation.dm" @@ -2371,7 +2373,6 @@ #include "code\game\objects\buckling.dm" #include "code\game\objects\empulse.dm" #include "code\game\objects\items.dm" -#include "code\game\objects\items_reskin.dm" #include "code\game\objects\obj_defense.dm" #include "code\game\objects\objs.dm" #include "code\game\objects\structures.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/ModifyPanel.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/ModifyPanel.tsx index 256b9650192..c2ca21aca6b 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/ModifyPanel.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/ModifyPanel.tsx @@ -104,7 +104,7 @@ function LoadoutModifyButtons(props: ButtonsProps) { {!!modifyItemDimmer.reskins && ( - + {modifyItemDimmer.reskins.map((reskin) => (