From 8510dcc08aa64ac12cff6f45dbc10979cb3e9d6e Mon Sep 17 00:00:00 2001 From: SandPoot <43283559+SandPoot@users.noreply.github.com> Date: Thu, 30 Dec 2021 03:40:15 -0300 Subject: [PATCH] Ports tgui stripping (finally) (#15401) * Upload files * Update items.dm * Update files * Update files * Update strippable.dm * Update strippable * Update strippable.dm * Upload files * Update files * Update files * Update files * update files * Update files * Update dog.dm * Update strippable.dm * Update equipping * Update strippable.d * Upload files * Upload files * Update monkey.dm --- code/__DEFINES/logging.dm | 5 +- code/__DEFINES/mobs.dm | 2 +- code/__DEFINES/strippable.dm | 31 ++ code/__HELPERS/_lists.dm | 8 +- code/__HELPERS/mobs.dm | 12 + code/datums/elements/mob_holder.dm | 2 +- code/datums/elements/strippable.dm | 517 ++++++++++++++++++ code/game/objects/items.dm | 13 + code/game/objects/structures/displaycase.dm | 2 +- code/modules/admin/outfit_editor.dm | 2 +- .../clockcult/clock_items/clockwork_armor.dm | 8 +- code/modules/asset_cache/asset_list_items.dm | 21 + code/modules/client/preferences.dm | 5 + code/modules/client/preferences_savefile.dm | 3 + code/modules/clothing/clothing.dm | 2 +- code/modules/clothing/spacesuits/hardsuit.dm | 2 +- .../living/carbon/alien/humanoid/humanoid.dm | 43 +- .../mob/living/carbon/alien/larva/larva.dm | 4 - code/modules/mob/living/carbon/carbon.dm | 53 -- .../mob/living/carbon/carbon_stripping.dm | 135 +++++ code/modules/mob/living/carbon/human/human.dm | 178 +----- .../living/carbon/human/human_stripping.dm | 306 +++++++++++ .../mob/living/carbon/monkey/monkey.dm | 12 + code/modules/mob/living/living.dm | 7 - .../mob/living/simple_animal/friendly/dog.dm | 282 ++++++---- .../mob/living/simple_animal/parrot.dm | 165 +++--- code/modules/mob/mob.dm | 19 - code/modules/vending/_vending.dm | 2 +- icons/UI_Icons/inventory/back.png | Bin 0 -> 1796 bytes icons/UI_Icons/inventory/belt.png | Bin 0 -> 1596 bytes icons/UI_Icons/inventory/collar.png | Bin 0 -> 2090 bytes icons/UI_Icons/inventory/ears.png | Bin 0 -> 1688 bytes icons/UI_Icons/inventory/glasses.png | Bin 0 -> 2017 bytes icons/UI_Icons/inventory/gloves.png | Bin 0 -> 2107 bytes icons/UI_Icons/inventory/hand_l.png | Bin 0 -> 2019 bytes icons/UI_Icons/inventory/hand_r.png | Bin 0 -> 2017 bytes icons/UI_Icons/inventory/head.png | Bin 0 -> 1854 bytes icons/UI_Icons/inventory/id.png | Bin 0 -> 1940 bytes icons/UI_Icons/inventory/mask.png | Bin 0 -> 1930 bytes icons/UI_Icons/inventory/neck.png | Bin 0 -> 1852 bytes icons/UI_Icons/inventory/pocket.png | Bin 0 -> 1831 bytes icons/UI_Icons/inventory/shoes.png | Bin 0 -> 1817 bytes icons/UI_Icons/inventory/suit.png | Bin 0 -> 2073 bytes icons/UI_Icons/inventory/suit_storage.png | Bin 0 -> 1976 bytes icons/UI_Icons/inventory/uniform.png | Bin 0 -> 1885 bytes tgstation.dme | 4 + tgui/packages/tgfont/icons/air-tank-slash.svg | 2 +- tgui/packages/tgui/interfaces/StripMenu.tsx | 182 +++++- .../tgui/styles/atomic/centered-image.scss | 7 + tgui/packages/tgui/styles/main.scss | 1 + 50 files changed, 1524 insertions(+), 513 deletions(-) create mode 100644 code/__DEFINES/strippable.dm create mode 100644 code/datums/elements/strippable.dm create mode 100644 code/modules/mob/living/carbon/carbon_stripping.dm create mode 100644 code/modules/mob/living/carbon/human/human_stripping.dm create mode 100644 icons/UI_Icons/inventory/back.png create mode 100644 icons/UI_Icons/inventory/belt.png create mode 100644 icons/UI_Icons/inventory/collar.png create mode 100644 icons/UI_Icons/inventory/ears.png create mode 100644 icons/UI_Icons/inventory/glasses.png create mode 100644 icons/UI_Icons/inventory/gloves.png create mode 100644 icons/UI_Icons/inventory/hand_l.png create mode 100644 icons/UI_Icons/inventory/hand_r.png create mode 100644 icons/UI_Icons/inventory/head.png create mode 100644 icons/UI_Icons/inventory/id.png create mode 100644 icons/UI_Icons/inventory/mask.png create mode 100644 icons/UI_Icons/inventory/neck.png create mode 100644 icons/UI_Icons/inventory/pocket.png create mode 100644 icons/UI_Icons/inventory/shoes.png create mode 100644 icons/UI_Icons/inventory/suit.png create mode 100644 icons/UI_Icons/inventory/suit_storage.png create mode 100644 icons/UI_Icons/inventory/uniform.png create mode 100644 tgui/packages/tgui/styles/atomic/centered-image.scss diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index ecb58f1291..bde3189272 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -40,15 +40,16 @@ #define LOG_ASAY (1 << 15) #define LOG_VIRUS (1 << 16) #define LOG_SHUTTLE (1 << 18) +#define LOG_VICTIM (1 << 19) //Individual logging panel pages -#define INDIVIDUAL_ATTACK_LOG (LOG_ATTACK) +#define INDIVIDUAL_ATTACK_LOG (LOG_ATTACK | LOG_VICTIM) #define INDIVIDUAL_SAY_LOG (LOG_SAY | LOG_WHISPER | LOG_DSAY) #define INDIVIDUAL_EMOTE_LOG (LOG_EMOTE | LOG_SUBTLER) #define INDIVIDUAL_COMMS_LOG (LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS) #define INDIVIDUAL_OOC_LOG (LOG_OOC | LOG_ADMIN) #define INDIVIDUAL_OWNERSHIP_LOG (LOG_OWNERSHIP) -#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME) +#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME | LOG_VICTIM) #define LOGSRC_CLIENT "Client" #define LOGSRC_MOB "Mob" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index bac40e3202..3ce91f4332 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -295,7 +295,7 @@ #define APPRENTICE_AGE_MIN 29 // youngest an apprentice can be #define SHOES_SLOWDOWN 0 // How much shoes slow you down by default. Negative values speed you up #define SHOES_SPEED_SLIGHT SHOES_SLOWDOWN - 1 // slightest speed boost to movement -#define POCKET_STRIP_DELAY 40 // time taken (in deciseconds) to search somebody's pockets +#define POCKET_STRIP_DELAY (4 SECONDS) //time taken to search somebody's pockets #define DOOR_CRUSH_DAMAGE 15 // the amount of damage that airlocks deal when they crush you #define HUNGER_FACTOR 0.1 // factor at which mob nutrition decreases diff --git a/code/__DEFINES/strippable.dm b/code/__DEFINES/strippable.dm new file mode 100644 index 0000000000..a660e82784 --- /dev/null +++ b/code/__DEFINES/strippable.dm @@ -0,0 +1,31 @@ +// All of these must be matched in StripMenu.js. +#define STRIPPABLE_ITEM_HEAD "head" +#define STRIPPABLE_ITEM_BACK "back" +#define STRIPPABLE_ITEM_MASK "mask" +#define STRIPPABLE_ITEM_NECK "neck" +#define STRIPPABLE_ITEM_EYES "eyes" +#define STRIPPABLE_ITEM_EARS "ears" +#define STRIPPABLE_ITEM_JUMPSUIT "jumpsuit" +#define STRIPPABLE_ITEM_SUIT "suit" +#define STRIPPABLE_ITEM_GLOVES "gloves" +#define STRIPPABLE_ITEM_FEET "shoes" +#define STRIPPABLE_ITEM_SUIT_STORAGE "suit_storage" +#define STRIPPABLE_ITEM_ID "id" +#define STRIPPABLE_ITEM_BELT "belt" +#define STRIPPABLE_ITEM_LPOCKET "left_pocket" +#define STRIPPABLE_ITEM_RPOCKET "right_pocket" +#define STRIPPABLE_ITEM_LHAND "left_hand" +#define STRIPPABLE_ITEM_RHAND "right_hand" +#define STRIPPABLE_ITEM_HANDCUFFS "handcuffs" +#define STRIPPABLE_ITEM_LEGCUFFS "legcuffs" +#define STRIPPABLE_ITEM_CORGI_COLLAR "corgi_collar" +#define STRIPPABLE_ITEM_PARROT_HEADSET "parrot_headset" + +/// This slot is not obscured. +#define STRIPPABLE_OBSCURING_NONE 0 + +/// This slot is completely obscured, and cannot be accessed. +#define STRIPPABLE_OBSCURING_COMPLETELY 1 + +/// This slot can't be seen, but can be accessed. +#define STRIPPABLE_OBSCURING_HIDDEN 2 diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 70793d7a1d..c858d30ba3 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -9,7 +9,7 @@ * Misc */ -#define LAZYINITLIST(L) if (!L) L = list() +#define LAZYINITLIST(L) if (!L) { L = list(); } #define UNSETEMPTY(L) if (L && !length(L)) L = null #define LAZYCOPY(L) (L ? L.Copy() : list() ) #define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } @@ -28,6 +28,12 @@ #define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } #define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null +/// Performs an insertion on the given lazy list with the given key and value. If the value already exists, a new one will not be made. +#define LAZYORASSOCLIST(lazy_list, key, value) \ + LAZYINITLIST(lazy_list); \ + LAZYINITLIST(lazy_list[key]); \ + lazy_list[key] |= value; + /// Passed into BINARY_INSERT to compare keys #define COMPARE_KEY __BIN_LIST[__BIN_MID] /// Passed into BINARY_INSERT to compare values diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 4693041f18..a26905f7b3 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -458,6 +458,18 @@ GLOBAL_LIST_EMPTY(species_datums) if(!HAS_TRAIT(L, TRAIT_PASSTABLE)) L.pass_flags &= ~PASSTABLE +/proc/dance_rotate(atom/movable/AM, datum/callback/callperrotate, set_original_dir=FALSE) + set waitfor = FALSE + var/originaldir = AM.dir + for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) + if(!AM) + return + AM.setDir(i) + callperrotate?.Invoke() + sleep(1) + if(set_original_dir) + AM.setDir(originaldir) + /// Gets the client of the mob, allowing for mocking of the client. /// You only need to use this if you know you're going to be mocking clients somewhere else. #define GET_CLIENT(mob) (##mob.client || ##mob.mock_client) diff --git a/code/datums/elements/mob_holder.dm b/code/datums/elements/mob_holder.dm index 6633ee3a47..0a56095437 100644 --- a/code/datums/elements/mob_holder.dm +++ b/code/datums/elements/mob_holder.dm @@ -168,7 +168,7 @@ L.visible_message("[held_mob] escapes from [L]!", "[held_mob] escapes your grip!") release() -/obj/item/clothing/head/mob_holder/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) +/obj/item/clothing/head/mob_holder/mob_can_equip(M, equipper, slot, disable_warning, bypass_equip_delay_self) if(M == held_mob || !ishuman(M)) //monkeys holding monkeys holding monkeys... return FALSE return ..() diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm new file mode 100644 index 0000000000..e67120f254 --- /dev/null +++ b/code/datums/elements/strippable.dm @@ -0,0 +1,517 @@ +/// An element for atoms that, when dragged and dropped onto a mob, opens a strip panel. +/datum/element/strippable + element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH + id_arg_index = 2 + + /// An assoc list of keys to /datum/strippable_item + var/list/items + + /// A proc path that returns TRUE/FALSE if we should show the strip panel for this entity. + /// If it does not exist, the strip menu will always show. + /// Will be called with (mob/user). + var/should_strip_proc_path + + /// An existing strip menus + var/list/strip_menus + +/datum/element/strippable/Attach(datum/target, list/items, should_strip_proc_path) + . = ..() + if (!isatom(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_MOUSEDROP_ONTO, .proc/mouse_drop_onto) + + src.items = items + src.should_strip_proc_path = should_strip_proc_path + +/datum/element/strippable/Detach(datum/source) + . = ..() + + UnregisterSignal(source, COMSIG_MOUSEDROP_ONTO) + + if (!isnull(strip_menus)) + qdel(strip_menus[source]) + strip_menus -= source + +/datum/element/strippable/proc/mouse_drop_onto(datum/source, atom/over, mob/user) + SIGNAL_HANDLER + + if (user == source) + return + + if (over != user) + return + + // Cyborgs buckle people by dragging them onto them, unless in combat mode. + if (iscyborg(user)) + var/mob/living/silicon/robot/cyborg_user = user + if (cyborg_user.a_intent == INTENT_HARM) + return + + if (!isnull(should_strip_proc_path) && !call(source, should_strip_proc_path)(user)) + return + + var/datum/strip_menu/strip_menu + + if (isnull(strip_menu)) + strip_menu = new(source, src) + LAZYSET(strip_menus, source, strip_menu) + + INVOKE_ASYNC(strip_menu, /datum/.proc/ui_interact, user) + +/// A representation of an item that can be stripped down +/datum/strippable_item + /// The STRIPPABLE_ITEM_* key + var/key + + /// Should we warn about dangerous clothing? + var/warn_dangerous_clothing = TRUE + +/// Gets the item from the given source. +/datum/strippable_item/proc/get_item(atom/source) + +/// Tries to equip the item onto the given source. +/// Returns TRUE/FALSE depending on if it is allowed. +/// This should be used for checking if an item CAN be equipped. +/// It should not perform the equipping itself. +/datum/strippable_item/proc/try_equip(atom/source, obj/item/equipping, mob/user) + if (HAS_TRAIT(equipping, TRAIT_NODROP)) + to_chat(user, span_warning("You can't put [equipping] on [source], it's stuck to your hand!")) + return FALSE + + return TRUE + +/// Start the equipping process. This is the proc you should yield in. +/// Returns TRUE/FALSE depending on if it is allowed. +/datum/strippable_item/proc/start_equip(atom/source, obj/item/equipping, mob/user) + if (warn_dangerous_clothing && isclothing(source)) + source.visible_message( + span_notice("[user] tries to put [equipping] on [source]."), + span_notice("[user] tries to put [equipping] on you."), + ignored_mobs = user, + ) + + if(ishuman(source)) + var/mob/living/carbon/human/victim_human = source + if(victim_human.key && !victim_human.client) // AKA braindead + if(victim_human.stat <= SOFT_CRIT && LAZYLEN(victim_human.afk_thefts) <= AFK_THEFT_MAX_MESSAGES) + var/list/new_entry = list(list(user.name, "tried equipping you with [equipping]", world.time)) + LAZYADD(victim_human.afk_thefts, new_entry) + + to_chat(user, span_notice("You try to put [equipping] on [source]...")) + + var/log = "[key_name(source)] is having [equipping] put on them by [key_name(user)]" + user.log_message(log, LOG_ATTACK, color="red") + source.log_message(log, LOG_VICTIM, color="red", log_globally=FALSE) + + return TRUE + +/// The proc that places the item on the source. This should not yield. +/datum/strippable_item/proc/finish_equip(atom/source, obj/item/equipping, mob/user) + SHOULD_NOT_SLEEP(TRUE) + return TRUE + +/// Tries to unequip the item from the given source. +/// Returns TRUE/FALSE depending on if it is allowed. +/// This should be used for checking if it CAN be unequipped. +/// It should not perform the unequipping itself. +/datum/strippable_item/proc/try_unequip(atom/source, mob/user) + SHOULD_NOT_SLEEP(TRUE) + + var/obj/item/item = get_item(source) + if (isnull(item)) + return FALSE + + if (ismob(source)) + var/mob/mob_source = source + if (!item.canStrip(user, mob_source)) + return FALSE + + return TRUE + +/// Start the unequipping process. This is the proc you should yield in. +/// Returns TRUE/FALSE depending on if it is allowed. +/datum/strippable_item/proc/start_unequip(atom/source, mob/user) + var/obj/item/item = get_item(source) + if (isnull(item)) + return FALSE + + source.visible_message( + span_warning("[user] tries to remove [source]'s [item.name]."), + span_userdanger("[user] tries to remove your [item.name]."), + ignored_mobs = user, + ) + + to_chat(user, span_danger("You try to remove [source]'s [item]...")) + user.log_message("[key_name(source)] is being stripped of [item] by [key_name(user)]", LOG_ATTACK, color="red") + source.log_message("[key_name(source)] is being stripped of [item] by [key_name(user)]", LOG_VICTIM, color="red", log_globally=FALSE) + item.add_fingerprint(source) + + if(ishuman(source)) + var/mob/living/carbon/human/victim_human = source + if(victim_human.key && !victim_human.client) // AKA braindead + if(victim_human.stat <= SOFT_CRIT && LAZYLEN(victim_human.afk_thefts) <= AFK_THEFT_MAX_MESSAGES) + var/list/new_entry = list(list(user.name, "tried unequipping your [item.name]", world.time)) + LAZYADD(victim_human.afk_thefts, new_entry) + + return TRUE + +/// The proc that unequips the item from the source. This should not yield. +/datum/strippable_item/proc/finish_unequip(atom/source, mob/user) + +/// Returns a STRIPPABLE_OBSCURING_* define to report on whether or not this is obscured. +/datum/strippable_item/proc/get_obscuring(atom/source) + SHOULD_NOT_SLEEP(TRUE) + return STRIPPABLE_OBSCURING_NONE + +/// Returns the ID of this item's strippable action. +/// Return `null` if there is no alternate action. +/// Any return value of this must be in StripMenu. +/datum/strippable_item/proc/get_alternate_action(atom/source, mob/user) + if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY) + return FALSE + return null + +/// Performs an alternative action on this strippable_item. +/// `has_alternate_action` needs to be TRUE. +/datum/strippable_item/proc/alternate_action(atom/source, mob/user) + if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY) + return null + return TRUE + +/// Returns whether or not this item should show. +/datum/strippable_item/proc/should_show(atom/source, mob/user) + return TRUE + +/// A preset for equipping items onto mob slots +/datum/strippable_item/mob_item_slot + /// The ITEM_SLOT_* to equip to. + var/item_slot + +/datum/strippable_item/mob_item_slot/get_item(atom/source) + if (!ismob(source)) + return null + + var/mob/mob_source = source + return mob_source.get_item_by_slot(item_slot) + +/datum/strippable_item/mob_item_slot/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return + + if (!ismob(source)) + return FALSE + + if (!equipping.mob_can_equip( + source, + user, + item_slot, + disable_warning = TRUE, + bypass_equip_delay_self = TRUE, + )) + to_chat(user, span_warning("\The [equipping] doesn't fit in that place!")) + return FALSE + + return TRUE + +/datum/strippable_item/mob_item_slot/start_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return + + if (!ismob(source)) + return FALSE + + if (!do_mob(user, source, get_equip_delay(equipping))) + return FALSE + + if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY) + return FALSE + + if (!equipping.mob_can_equip( + source, + user, + item_slot, + disable_warning = TRUE, + bypass_equip_delay_self = TRUE, + )) + return FALSE + + if (!user.temporarilyRemoveItemFromInventory(equipping)) + return FALSE + + return TRUE + +/datum/strippable_item/mob_item_slot/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + if (!ismob(source)) + return FALSE + + var/mob/mob_source = source + mob_source.equip_to_slot(equipping, item_slot) + +/datum/strippable_item/mob_item_slot/get_obscuring(atom/source) + if (iscarbon(source)) + var/mob/living/carbon/carbon_source = source + return (item_slot in carbon_source.check_obscured_slots()) \ + ? STRIPPABLE_OBSCURING_COMPLETELY \ + : STRIPPABLE_OBSCURING_NONE + + return FALSE + +/datum/strippable_item/mob_item_slot/start_unequip(atom/source, mob/user) + . = ..() + if (!.) + return + + return start_unequip_mob(get_item(source), source, user) + +/datum/strippable_item/mob_item_slot/finish_unequip(atom/source, mob/user) + var/obj/item/item = get_item(source) + if (isnull(item)) + return FALSE + + if (!ismob(source)) + return FALSE + + return finish_unequip_mob(item, source, user) + +/// Returns the delay of equipping this item to a mob +/datum/strippable_item/mob_item_slot/proc/get_equip_delay(obj/item/equipping) + return equipping.equip_delay_other + +/// A utility function for `/datum/strippable_item`s to start unequipping an item from a mob. +/proc/start_unequip_mob(obj/item/item, mob/source, mob/user, strip_delay) + if (!do_mob(user, source, strip_delay || item.strip_delay, ignorehelditem = TRUE)) + return FALSE + + return TRUE + +/// A utility function for `/datum/strippable_item`s to finish unequipping an item from a mob. +/proc/finish_unequip_mob(obj/item/item, mob/source, mob/user) + if (!item.doStrip(user, source)) + return FALSE + + user.log_message("[key_name(source)] has been stripped of [item] by [key_name(user)]", LOG_ATTACK, color="red") + source.log_message("[key_name(source)] has been stripped of [item] by [key_name(user)]", LOG_VICTIM, color="red", log_globally=FALSE) + + // Updates speed in case stripped speed affecting item + source.update_equipment_speed_mods() + +/// A representation of the stripping UI +/datum/strip_menu + /// The owner who has the element /datum/element/strippable + var/atom/movable/owner + + /// The strippable element itself + var/datum/element/strippable/strippable + + /// A lazy list of user mobs to a list of strip menu keys that they're interacting with + var/list/interactions + +/datum/strip_menu/New(atom/movable/owner, datum/element/strippable/strippable) + . = ..() + src.owner = owner + src.strippable = strippable + +/datum/strip_menu/Destroy() + owner = null + strippable = null + + return ..() + +/datum/strip_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "StripMenu") + ui.open() + +/datum/strip_menu/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/inventory), + ) + +/datum/strip_menu/ui_data(mob/user) + var/list/data = list() + + var/list/items = list() + + for (var/strippable_key in strippable.items) + var/datum/strippable_item/item_data = strippable.items[strippable_key] + + if (!item_data.should_show(owner, user)) + continue + + var/list/result + + if(strippable_key in LAZYACCESS(interactions, user)) + LAZYSET(result, "interacting", TRUE) + + var/obscuring = item_data.get_obscuring(owner) + if (obscuring != STRIPPABLE_OBSCURING_NONE) + LAZYSET(result, "obscured", obscuring) + items[strippable_key] = result + continue + + var/obj/item/item = item_data.get_item(owner) + if (isnull(item)) + items[strippable_key] = result + continue + + LAZYINITLIST(result) + + result["icon"] = icon2base64(icon(item.icon, item.icon_state, SOUTH, 1)) + result["name"] = item.name + result["alternate"] = item_data.get_alternate_action(owner, user) + + items[strippable_key] = result + + data["items"] = items + + // While most `\the`s are implicit, this one is not. + // In this case, `\The` would otherwise be used. + // This doesn't match with what it's used for, which is to say "Stripping the alien drone", + // as opposed to "Stripping The alien drone". + // Human names will still show without "the", as they are proper nouns. + data["name"] = "\the [owner]" + + /// Customize the strip menu + data["long_strip_menu"] = user.client.prefs.long_strip_menu + + return data + +/datum/strip_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if (.) + return + + . = TRUE + + var/mob/user = usr + + switch (action) + if ("use") + var/key = params["key"] + var/datum/strippable_item/strippable_item = strippable.items[key] + + if (isnull(strippable_item)) + return + + if (!strippable_item.should_show(owner, user)) + return + + if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY) + return + + var/item = strippable_item.get_item(owner) + if (isnull(item)) + var/obj/item/held_item = user.get_active_held_item() + if (isnull(held_item)) + return + + if (strippable_item.try_equip(owner, held_item, user)) + LAZYORASSOCLIST(interactions, user, key) + + // Yielding call + var/should_finish = strippable_item.start_equip(owner, held_item, user) + + LAZYREMOVEASSOC(interactions, user, key) + + if (!should_finish) + return + + if (QDELETED(src) || QDELETED(owner)) + return + + // They equipped an item in the meantime + if (!isnull(strippable_item.get_item(owner))) + return + + if (!user.Adjacent(owner)) + return + + strippable_item.finish_equip(owner, held_item, user) + else if (strippable_item.try_unequip(owner, user)) + LAZYORASSOCLIST(interactions, user, key) + + var/should_unequip = strippable_item.start_unequip(owner, user) + + LAZYREMOVEASSOC(interactions, user, key) + + // Yielding call + if (!should_unequip) + return + + if (QDELETED(src) || QDELETED(owner)) + return + + // They changed the item in the meantime + if (strippable_item.get_item(owner) != item) + return + + if (!user.Adjacent(owner)) + return + + strippable_item.finish_unequip(owner, user) + if ("alt") + var/key = params["key"] + var/datum/strippable_item/strippable_item = strippable.items[key] + + if (isnull(strippable_item)) + return + + if (!strippable_item.should_show(owner, user)) + return + + if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY) + return + + var/item = strippable_item.get_item(owner) + if (isnull(item)) + return + + if (isnull(strippable_item.get_alternate_action(owner, user))) + return + + LAZYORASSOCLIST(interactions, user, key) + + // Potentially yielding + strippable_item.alternate_action(owner, user) + + LAZYREMOVEASSOC(interactions, user, key) + +/datum/strip_menu/ui_host(mob/user) + return owner + +/datum/strip_menu/ui_state(mob/user) + return GLOB.always_state + +/datum/strip_menu/ui_status(mob/user, datum/ui_state/state) + . = ..() + + if(isliving(user)) + var/mob/living/living_user = user + + if ( + living_user.stat == CONSCIOUS \ + && living_user.Adjacent(owner) + ) + return UI_INTERACTIVE + if(IsAdminGhost(user)) + return UI_INTERACTIVE + if(user.Adjacent(owner)) + return UI_UPDATE + else + return UI_DISABLED + +/// Creates an assoc list of keys to /datum/strippable_item +/proc/create_strippable_list(types) + var/list/strippable_items = list() + + for (var/strippable_type in types) + var/datum/strippable_item/strippable_item = new strippable_type + strippable_items[strippable_item.key] = strippable_item + + return strippable_items diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index b320db0a20..fe5950604b 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1071,6 +1071,19 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb . = ..() if(var_name == NAMEOF(src, slowdown)) set_slowdown(var_value) //don't care if it's a duplicate edit as slowdown'll be set, do it anyways to force normal behavior. + +/obj/item/proc/canStrip(mob/stripper, mob/owner) + SHOULD_BE_PURE(TRUE) + return !HAS_TRAIT(src, TRAIT_NODROP) && !(item_flags & ABSTRACT) + +/obj/item/proc/doStrip(mob/stripper, mob/owner) + if(owner.dropItemToGround(src)) + if(stripper.can_hold_items()) + stripper.put_in_hands(src) + return TRUE + else + return FALSE + /** * Does the current embedding var meet the criteria for being harmless? Namely, does it explicitly define the pain multiplier and jostle pain mult to be 0? If so, return true. * diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index c32bf81ecf..ce2acdbdf7 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -397,7 +397,7 @@ data["owner_name"] = payments_acc.account_holder if(showpiece) data["product_name"] = capitalize(showpiece.name) - var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state)) + var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state, SOUTH, 1)) data["product_icon"] = base64 data["registered"] = register data["product_cost"] = sale_price diff --git a/code/modules/admin/outfit_editor.dm b/code/modules/admin/outfit_editor.dm index 9a99d8b20e..aa53aade13 100644 --- a/code/modules/admin/outfit_editor.dm +++ b/code/modules/admin/outfit_editor.dm @@ -55,7 +55,7 @@ "name" = initial(item.name), "desc" = initial(item.desc), // at this point initializing the item is probably faster tbh - "sprite" = icon2base64(icon(initial(item.icon), initial(item.icon_state))), + "sprite" = icon2base64(icon(initial(item.icon), initial(item.icon_state), SOUTH, 1)), ) return data diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm index d08caa39d7..ad507adcec 100644 --- a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm +++ b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm @@ -53,7 +53,7 @@ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30) addtimer(CALLBACK(user, /mob/living.proc/dropItemToGround, src, TRUE), 1) //equipped happens before putting stuff on(but not before picking items up), 1). thus, we need to wait for it to be on before forcing it off. -/obj/item/clothing/head/helmet/clockwork/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0) +/obj/item/clothing/head/helmet/clockwork/mob_can_equip(M, equipper, slot, disable_warning, bypass_equip_delay_self) if(equipper && !is_servant_of_ratvar(equipper)) return 0 return ..() @@ -98,7 +98,7 @@ max_heat_protection_temperature = initial(max_heat_protection_temperature) min_cold_protection_temperature = initial(min_cold_protection_temperature) -/obj/item/clothing/suit/armor/clockwork/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0) +/obj/item/clothing/suit/armor/clockwork/mob_can_equip(M, equipper, slot, disable_warning, bypass_equip_delay_self) if(equipper && !is_servant_of_ratvar(equipper)) return 0 return ..() @@ -158,7 +158,7 @@ max_heat_protection_temperature = initial(max_heat_protection_temperature) min_cold_protection_temperature = initial(min_cold_protection_temperature) -/obj/item/clothing/gloves/clockwork/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0) +/obj/item/clothing/gloves/clockwork/mob_can_equip(M, equipper, slot, disable_warning, bypass_equip_delay_self) if(equipper && !is_servant_of_ratvar(equipper)) return 0 return ..() @@ -208,7 +208,7 @@ else clothing_flags &= ~NOSLIP -/obj/item/clothing/shoes/clockwork/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0) +/obj/item/clothing/shoes/clockwork/mob_can_equip(M, equipper, slot, disable_warning, bypass_equip_delay_self) if(equipper && !is_servant_of_ratvar(equipper)) return 0 return ..() diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 2d680fe212..6cf41d666f 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -543,6 +543,27 @@ // Insert(id, fish_icon, fish_icon_state) // ..() +/datum/asset/simple/inventory + assets = list( + "inventory-glasses.png" = 'icons/UI_Icons/inventory/glasses.png', + "inventory-head.png" = 'icons/UI_Icons/inventory/head.png', + "inventory-neck.png" = 'icons/UI_Icons/inventory/neck.png', + "inventory-mask.png" = 'icons/UI_Icons/inventory/mask.png', + "inventory-ears.png" = 'icons/UI_Icons/inventory/ears.png', + "inventory-uniform.png" = 'icons/UI_Icons/inventory/uniform.png', + "inventory-suit.png" = 'icons/UI_Icons/inventory/suit.png', + "inventory-gloves.png" = 'icons/UI_Icons/inventory/gloves.png', + "inventory-hand_l.png" = 'icons/UI_Icons/inventory/hand_l.png', + "inventory-hand_r.png" = 'icons/UI_Icons/inventory/hand_r.png', + "inventory-shoes.png" = 'icons/UI_Icons/inventory/shoes.png', + "inventory-suit_storage.png" = 'icons/UI_Icons/inventory/suit_storage.png', + "inventory-id.png" = 'icons/UI_Icons/inventory/id.png', + "inventory-belt.png" = 'icons/UI_Icons/inventory/belt.png', + "inventory-back.png" = 'icons/UI_Icons/inventory/back.png', + "inventory-pocket.png" = 'icons/UI_Icons/inventory/pocket.png', + "inventory-collar.png" = 'icons/UI_Icons/inventory/collar.png', + ) + /// Removes all non-alphanumerics from the text, keep in mind this can lead to id conflicts /proc/sanitize_css_class_name(name) var/static/regex/regex = new(@"[^a-zA-Z0-9]","g") diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index cb262e57f3..94b839250d 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -171,6 +171,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/auto_fit_viewport = FALSE ///Should we be in the widescreen mode set by the config? var/widescreenpref = TRUE + ///Strip menu style + var/long_strip_menu = FALSE ///What size should pixels be displayed as? 0 is strech to fit var/pixel_size = 0 ///What scaling method should we use? @@ -829,6 +831,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat +="" dat += "

Citadel Preferences

" //Because fuck me if preferences can't be fucking modularized and expected to update in a reasonable timeframe. dat += "Widescreen: [widescreenpref ? "Enabled ([CONFIG_GET(string/default_view)])" : "Disabled (15x15)"]
" + dat += "Long strip menu: [long_strip_menu ? "Enabled" : "Disabled"]
" dat += "Auto stand: [autostand ? "Enabled" : "Disabled"]
" dat += "Auto OOC: [auto_ooc ? "Enabled" : "Disabled"]
" dat += "Force Slot Storage HUD: [no_tetris_storage ? "Enabled" : "Disabled"]
" @@ -2591,6 +2594,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) if("widescreenpref") widescreenpref = !widescreenpref user.client.view_size.setDefault(getScreenSize(widescreenpref)) + if("long_strip_menu") + long_strip_menu = !long_strip_menu if("pixel_size") switch(pixel_size) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 510ac9ff28..140360ff85 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -410,6 +410,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["ambientocclusion"] >> ambientocclusion S["auto_fit_viewport"] >> auto_fit_viewport S["widescreenpref"] >> widescreenpref + S["long_strip_menu"] >> long_strip_menu S["pixel_size"] >> pixel_size S["scaling_method"] >> scaling_method S["hud_toggle_flash"] >> hud_toggle_flash @@ -472,6 +473,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car ambientocclusion = sanitize_integer(ambientocclusion, 0, 1, initial(ambientocclusion)) auto_fit_viewport = sanitize_integer(auto_fit_viewport, 0, 1, initial(auto_fit_viewport)) widescreenpref = sanitize_integer(widescreenpref, 0, 1, initial(widescreenpref)) + long_strip_menu = sanitize_integer(long_strip_menu, 0, 1, initial(long_strip_menu)) pixel_size = sanitize_integer(pixel_size, PIXEL_SCALING_AUTO, PIXEL_SCALING_3X, initial(pixel_size)) scaling_method = sanitize_text(scaling_method, initial(scaling_method)) hud_toggle_flash = sanitize_integer(hud_toggle_flash, 0, 1, initial(hud_toggle_flash)) @@ -604,6 +606,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["damagescreenshake"], damagescreenshake) WRITE_FILE(S["arousable"], arousable) WRITE_FILE(S["widescreenpref"], widescreenpref) + WRITE_FILE(S["long_strip_menu"], long_strip_menu) WRITE_FILE(S["autostand"], autostand) WRITE_FILE(S["cit_toggles"], cit_toggles) WRITE_FILE(S["preferred_chaos"], preferred_chaos) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 3beeef91d3..1a7c220550 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -443,7 +443,7 @@ BLIND // can't see anything ..() //Species-restricted clothing check. - Thanks Oraclestation, BS13, /vg/station etc. -/obj/item/clothing/mob_can_equip(mob/M, slot, disable_warning = TRUE) +/obj/item/clothing/mob_can_equip(M, equipper, slot, disable_warning = TRUE, bypass_equip_delay_self) //if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam) if(!..()) diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index cd48a81350..33204f377b 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -633,7 +633,7 @@ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/clown mutantrace_variation = STYLE_DIGITIGRADE -/obj/item/clothing/suit/space/hardsuit/clown/mob_can_equip(mob/M, slot) +/obj/item/clothing/suit/space/hardsuit/clown/mob_can_equip(mob/M, equipper, slot, disable_warning, bypass_equip_delay_self) if(!..() || !ishuman(M)) return FALSE if(M.mind && HAS_TRAIT(M.mind, TRAIT_CLOWN_MENTALITY)) diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index 0d2a1c0c59..343b95f05d 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -6,8 +6,6 @@ possible_a_intents = list(INTENT_HELP, INTENT_DISARM, INTENT_GRAB, INTENT_HARM) limb_destroyer = 1 hud_type = /datum/hud/alien - var/obj/item/r_store = null - var/obj/item/l_store = null var/caste = "" var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. var/leap_on_click = 0 @@ -22,41 +20,26 @@ can_ventcrawl = TRUE +GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( + /datum/strippable_item/hand/left, + /datum/strippable_item/hand/right, + /datum/strippable_item/mob_item_slot/handcuffs, + /datum/strippable_item/mob_item_slot/legcuffs, +))) //This is fine right now, if we're adding organ specific damage this needs to be updated /mob/living/carbon/alien/humanoid/Initialize() AddAbility(new/obj/effect/proc_holder/alien/regurgitate(null)) . = ..() + +/mob/living/carbon/alien/humanoid/ComponentInitialize() + . = ..() AddComponent(/datum/component/footstep, FOOTSTEP_MOB_CLAW, 0.5, -3) + AddElement(/datum/element/strippable, GLOB.strippable_alien_humanoid_items) /mob/living/carbon/alien/humanoid/restrained(ignore_grab) return handcuffed -/mob/living/carbon/alien/humanoid/show_inv(mob/user) - user.set_machine(src) - var/list/dat = list() - dat += {" -
- [name] -
"} - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "
[get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]" - dat += "
Empty Pouches" - - if(handcuffed) - dat += "
Handcuffed" - if(legcuffed) - dat += "
Legcuffed" - - dat += {" -
-
Close - "} - user << browse(dat.Join(), "window=mob[REF(src)];size=325x500") - onclose(user, "mob[REF(src)]") - - /mob/living/carbon/alien/humanoid/Topic(href, href_list) ..() //strip panel & embeds @@ -70,12 +53,6 @@ return SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L) return - if(href_list["pouches"]) - visible_message("[usr] tries to empty [src]'s pouches.", \ - "[usr] tries to empty [src]'s pouches.") - if(do_mob(usr, src, POCKET_STRIP_DELAY * 0.5)) - dropItemToGround(r_store) - dropItemToGround(l_store) /mob/living/carbon/alien/humanoid/cuff_resist(obj/item/I) playsound(src, 'sound/voice/hiss5.ogg', 40, 1, 1) //Alien roars when starting to break free diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index 77c9a8c579..7a610ac421 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -51,10 +51,6 @@ // new damage icon system // now constructs damage icon for each organ from mask * damage field - -/mob/living/carbon/alien/larva/show_inv(mob/user) - return - /mob/living/carbon/alien/larva/toggle_throw_mode() return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 0700cba2ea..6acd604ca5 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -229,61 +229,8 @@ /mob/living/carbon/proc/canBeHandcuffed() return 0 - -/mob/living/carbon/show_inv(mob/user) - user.set_machine(src) - var/dat = {" -
- [name] -
-
Head: [(head && !(head.item_flags & ABSTRACT)) ? head : "Nothing"] -
Mask: [(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Nothing"] -
Neck: [(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Nothing"]"} - - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "
[get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Nothing"]" - - dat += "
Back: [back ? back : "Nothing"]" - - if(!HAS_TRAIT(src, TRAIT_NO_INTERNALS) && istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank)) - dat += "
[internal ? "Disable Internals" : "Set Internals"]" - - if(handcuffed) - dat += "
Handcuffed" - if(legcuffed) - dat += "
Legcuffed" - - dat += {" -
-
Close - "} - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "mob[REF(src)]") - /mob/living/carbon/Topic(href, href_list) ..() - //strip panel - if(usr.canUseTopic(src, BE_CLOSE)) - if(href_list["internal"] && !HAS_TRAIT(src, TRAIT_NO_INTERNALS)) - var/slot = text2num(href_list["internal"]) - var/obj/item/ITEM = get_item_by_slot(slot) - if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) - visible_message("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].", \ - "[usr] tries to [internal ? "close" : "open"] the valve on your [ITEM.name].", \ - target = usr, target_message = "You try to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].") - if(do_mob(usr, src, POCKET_STRIP_DELAY)) - if(internal) - internal = null - update_internals_hud_icon(0) - else if(ITEM && istype(ITEM, /obj/item/tank)) - if((wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - internal = ITEM - update_internals_hud_icon(1) - - visible_message("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].", \ - "[usr] [internal ? "opens" : "closes"] the valve on your [ITEM.name].", \ - target = usr, target_message = "You [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].") if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE)) var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts if(!L) diff --git a/code/modules/mob/living/carbon/carbon_stripping.dm b/code/modules/mob/living/carbon/carbon_stripping.dm new file mode 100644 index 0000000000..9e005e2210 --- /dev/null +++ b/code/modules/mob/living/carbon/carbon_stripping.dm @@ -0,0 +1,135 @@ +/datum/strippable_item/mob_item_slot/head + key = STRIPPABLE_ITEM_HEAD + item_slot = SLOT_HEAD + +/datum/strippable_item/mob_item_slot/back + key = STRIPPABLE_ITEM_BACK + item_slot = SLOT_BACK + +/datum/strippable_item/mob_item_slot/mask + key = STRIPPABLE_ITEM_MASK + item_slot = SLOT_WEAR_MASK + +/datum/strippable_item/mob_item_slot/neck + key = STRIPPABLE_ITEM_NECK + item_slot = SLOT_NECK + +/datum/strippable_item/mob_item_slot/handcuffs + key = STRIPPABLE_ITEM_HANDCUFFS + item_slot = SLOT_HANDCUFFED + +/datum/strippable_item/mob_item_slot/handcuffs/should_show(atom/source, mob/user) + if (!iscarbon(source)) + return FALSE + + var/mob/living/carbon/carbon_source = source + return !isnull(carbon_source.handcuffed) + +// You shouldn't be able to equip things to handcuff slots. +/datum/strippable_item/mob_item_slot/handcuffs/try_equip(atom/source, obj/item/equipping, mob/user) + return FALSE + +/datum/strippable_item/mob_item_slot/legcuffs + key = STRIPPABLE_ITEM_LEGCUFFS + item_slot = SLOT_LEGCUFFED + +/datum/strippable_item/mob_item_slot/legcuffs/should_show(atom/source, mob/user) + if (!iscarbon(source)) + return FALSE + + var/mob/living/carbon/carbon_source = source + return !isnull(carbon_source.legcuffed) + +// You shouldn't be able to equip things to legcuff slots. +/datum/strippable_item/mob_item_slot/legcuffs/try_equip(atom/source, obj/item/equipping, mob/user) + return FALSE + +/// A strippable item for a hand +/datum/strippable_item/hand + // Putting dangerous clothing in our hand is fine. + warn_dangerous_clothing = FALSE + + /// Which hand? + var/hand_index + +/datum/strippable_item/hand/get_item(atom/source) + if (!ismob(source)) + return null + + var/mob/mob_source = source + return mob_source.get_item_for_held_index(hand_index) + +/datum/strippable_item/hand/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE + + if (!ismob(source)) + return FALSE + + var/mob/mob_source = source + + if (!mob_source.can_put_in_hand(equipping, hand_index)) + to_chat(src, "\The [equipping] doesn't fit in that place!") + return FALSE + + return TRUE + +/datum/strippable_item/hand/start_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return + + if (!ismob(source)) + return FALSE + + var/mob/mob_source = source + + if (!do_mob(user, source, equipping.equip_delay_other)) + return FALSE + + if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY) + return FALSE + + if (!mob_source.can_put_in_hand(equipping, hand_index)) + return FALSE + + if (!user.temporarilyRemoveItemFromInventory(equipping)) + return FALSE + + return TRUE + +/datum/strippable_item/hand/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + if (!iscarbon(source)) + return FALSE + + var/mob/mob_source = source + mob_source.put_in_hand(equipping, hand_index) + +/datum/strippable_item/hand/start_unequip(atom/source, mob/user) + . = ..() + if (!.) + return + + return start_unequip_mob(get_item(source), source, user) + +/datum/strippable_item/hand/finish_unequip(atom/source, mob/user) + ..() + var/obj/item/item = get_item(source) + if (isnull(item)) + return FALSE + + if (!ismob(source)) + return FALSE + + return finish_unequip_mob(item, source, user) + +/datum/strippable_item/hand/left + key = STRIPPABLE_ITEM_LHAND + hand_index = 1 + +/datum/strippable_item/hand/right + key = STRIPPABLE_ITEM_RHAND + hand_index = 2 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 8669a1458a..4fc60cc6ac 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -47,6 +47,7 @@ AddElement(/datum/element/flavor_text/carbon, _name = "Flavor Text", _save_key = "flavor_text") AddElement(/datum/element/flavor_text/carbon/temporary, "", "Set Pose (Temporary Flavor Text)", "This should be used only for things pertaining to the current round!", _save_key = null) AddElement(/datum/element/flavor_text, _name = "OOC Notes", _addendum = "Put information on ERP/vore/lewd-related preferences here. THIS SHOULD NOT CONTAIN REGULAR FLAVORTEXT!!", _always_show = TRUE, _save_key = "ooc_notes", _examine_no_preview = TRUE) + AddElement(/datum/element/strippable, GLOB.strippable_human_items, /mob/living/carbon/human/.proc/should_strip) /mob/living/carbon/human/Destroy() QDEL_NULL(physiology) @@ -110,106 +111,6 @@ var/datum/disease/D = thing . += "* [D.name], Type: [D.spread_text], Stage: [D.stage]/[D.max_stages], Possible Cure: [D.cure_text]" -/mob/living/carbon/human/show_inv(mob/user) - user.set_machine(src) - var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) - var/list/obscured = check_obscured_slots() - var/list/dat = list() - - dat += "" - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "" - dat += "" - - dat += "" - - dat += "" - - if(SLOT_WEAR_MASK in obscured) - dat += "" - else - dat += "" - - if(SLOT_NECK in obscured) - dat += "" - else - dat += "" - - if(SLOT_GLASSES in obscured) - dat += "" - else - dat += "" - - if(SLOT_EARS in obscured) - dat += "" - else - dat += "" - - dat += "" - - dat += "" - dat += "" - else - dat += "" - - if(SLOT_SHOES in obscured) - dat += "" - else - dat += "" - - if(SLOT_GLOVES in obscured) - dat += "" - else - dat += "" - - if(SLOT_W_UNIFORM in obscured) - dat += "" - else - dat += "" - - if((w_uniform == null && !(dna && dna.species.nojumpsuit)) || (SLOT_W_UNIFORM in obscured)) - dat += "" - dat += "" - dat += "" - else - dat += "" - dat += "" - dat += "" - - if(handcuffed) - dat += "" - if(legcuffed) - dat += "" - - dat += {"
[get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]
 
Back:[(back && !(back.item_flags & ABSTRACT)) ? back : "Empty"]" - if(has_breathable_mask && istype(back, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - - dat += "
 
Head:[(head && !(head.item_flags & ABSTRACT)) ? head : "Empty"]
Mask:Obscured
Mask:[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Empty"]
Neck:Obscured
Neck:[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Empty"]
Eyes:Obscured
Eyes:[(glasses && !(glasses.item_flags & ABSTRACT)) ? glasses : "Empty"]
Ears:Obscured
Ears:[(ears && !(ears.item_flags & ABSTRACT)) ? ears : "Empty"]
 
Exosuit:[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "Empty"]" - if(wear_suit) - if(istype(wear_suit, /obj/item/clothing/suit/space/hardsuit)) - var/hardsuit_head = head && istype(head, /obj/item/clothing/head/helmet/space/hardsuit) - dat += " [hardsuit_head ? "Retract Helmet" : "Extend Helmet"]" - dat += "
 ↳Suit Storage:[(s_store && !(s_store.item_flags & ABSTRACT)) ? s_store : "Empty"]" - if(has_breathable_mask && istype(s_store, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
 ↳Suit Storage:
Shoes:Obscured
Shoes:[(shoes && !(shoes.item_flags & ABSTRACT)) ? shoes : "Empty"]" - if(shoes && shoes.can_be_tied && shoes.tied != SHOES_KNOTTED) - dat += " [shoes.tied ? "Untie shoes" : "Knot shoes"]" - - dat += "
Gloves:Obscured
Gloves:[(gloves && !(gloves.item_flags & ABSTRACT)) ? gloves : "Empty"]
Uniform:Obscured
Uniform:[(w_uniform && !(w_uniform.item_flags & ABSTRACT)) ? w_uniform : "Empty"]
 ↳Pockets:
 ↳ID:
 ↳Belt:
 ↳Belt:[(belt && !(belt.item_flags & ABSTRACT)) ? belt : "Empty"]" - if(has_breathable_mask && istype(belt, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
 ↳Pockets:[(l_store && !(l_store.item_flags & ABSTRACT)) ? "Left (Full)" : "Left (Empty)"]" - dat += " [(r_store && !(r_store.item_flags & ABSTRACT)) ? "Right (Full)" : "Right (Empty)"]
 ↳ID:[(wear_id && !(wear_id.item_flags & ABSTRACT)) ? wear_id : "Empty"]
Handcuffed: Remove
Legcuffed
- Close - "} - - var/datum/browser/popup = new(user, "mob[REF(src)]", "[src]", 440, 510) - popup.set_content(dat.Join()) - popup.open() - // called when something steps onto a human // this could be made more general, but for now just handle mulebot /mob/living/carbon/human/Crossed(atom/movable/AM) @@ -231,83 +132,6 @@ return SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L) return - if(href_list["toggle_helmet"]) - if(!istype(head, /obj/item/clothing/head/helmet/space/hardsuit)) - return - var/obj/item/clothing/head/helmet/space/hardsuit/hardsuit_head = head - visible_message("[usr] tries to [hardsuit_head ? "retract" : "extend"] [src]'s helmet.", \ - "[usr] tries to [hardsuit_head ? "retract" : "extend"] [src]'s helmet.", \ - target = usr, target_message = "You try to [hardsuit_head ? "retract" : "extend"] [src]'s helmet.") - if(!do_mob(usr, src, hardsuit_head ? head.strip_delay : POCKET_STRIP_DELAY)) - return - if(!istype(wear_suit, /obj/item/clothing/suit/space/hardsuit) || (hardsuit_head ? (!head || head != hardsuit_head) : head)) - return - var/obj/item/clothing/suit/space/hardsuit/hardsuit = wear_suit //This should be an hardsuit given all our checks - if(hardsuit.ToggleHelmet(FALSE)) - visible_message("[usr] [hardsuit_head ? "retract" : "extend"] [src]'s helmet", \ - "[usr] [hardsuit_head ? "retract" : "extend"] [src]'s helmet", \ - target = usr, target_message = "You [hardsuit_head ? "retract" : "extend"] [src]'s helmet.") - return - if(href_list["item"]) - var/slot = text2num(href_list["item"]) - if(slot in check_obscured_slots()) - to_chat(usr, "You can't reach that! Something is covering it.") - return - if(href_list["pockets"]) - var/strip_mod = 1 - var/strip_silence = FALSE - var/obj/item/clothing/gloves/G = gloves - if(istype(G)) - strip_mod = G.strip_mod - strip_silence = G.strip_silence - var/pocket_side = href_list["pockets"] - var/pocket_id = (pocket_side == "right" ? SLOT_R_STORE : SLOT_L_STORE) - var/obj/item/pocket_item = (pocket_id == SLOT_R_STORE ? r_store : l_store) - var/obj/item/place_item = usr.get_active_held_item() // Item to place in the pocket, if it's empty - - var/delay_denominator = 1 - if(pocket_item && !(pocket_item.item_flags & ABSTRACT)) - if(HAS_TRAIT(pocket_item, TRAIT_NODROP)) - to_chat(usr, "You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!") - to_chat(usr, "You try to empty [src]'s [pocket_side] pocket.") - else if(place_item && place_item.mob_can_equip(src, usr, pocket_id, 1) && !(place_item.item_flags & ABSTRACT)) - to_chat(usr, "You try to place [place_item] into [src]'s [pocket_side] pocket.") - delay_denominator = 4 - else - return - - if(do_mob(usr, src, max(round(POCKET_STRIP_DELAY/(delay_denominator*strip_mod)),1), ignorehelditem = TRUE)) //placing an item into the pocket is 4 times faster (and the strip_mod too) - if(pocket_item) - if(pocket_item == (pocket_id == SLOT_R_STORE ? r_store : l_store)) //item still in the pocket we search - dropItemToGround(pocket_item) - if(!usr.can_hold_items() || !usr.put_in_hands(pocket_item)) - pocket_item.forceMove(drop_location()) - log_combat(usr, src, "pickpocketed of item: [pocket_item]") - else - if(place_item) - if(place_item.mob_can_equip(src, usr, pocket_id, FALSE, TRUE)) - usr.temporarilyRemoveItemFromInventory(place_item, TRUE) - equip_to_slot(place_item, pocket_id, TRUE) - log_combat(usr, src, "placed item [place_item] onto") - //do nothing otherwise - - // Update strip window - if(usr.machine == src && in_range(src, usr)) - show_inv(usr) - else - // Display a warning if the user mocks up - if(!strip_silence) - to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") - log_combat(usr, src, "failed to [pocket_item ? "pickpocket item [pocket_item] from" : "place item [place_item] onto "]") - - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY, null, FALSE)) - // separate from first canusetopic - var/mob/living/user = usr - if(istype(user) && href_list["shoes"] && (user.mobility_flags & MOBILITY_USE)) // we need to be on the ground, so we'll be a bit looser - shoes.handle_tying(usr) - - ..() //CITADEL CHANGE - removes a tab from behind this ..() so that flavortext can actually be examined - ///////HUDs/////// if(href_list["hud"]) diff --git a/code/modules/mob/living/carbon/human/human_stripping.dm b/code/modules/mob/living/carbon/human/human_stripping.dm new file mode 100644 index 0000000000..6197241ef3 --- /dev/null +++ b/code/modules/mob/living/carbon/human/human_stripping.dm @@ -0,0 +1,306 @@ +#define INTERNALS_TOGGLE_DELAY (4 SECONDS) +#define POCKET_EQUIP_DELAY (1 SECONDS) + +GLOBAL_LIST_INIT(strippable_human_items, create_strippable_list(list( + /datum/strippable_item/mob_item_slot/head, + /datum/strippable_item/mob_item_slot/back, + /datum/strippable_item/mob_item_slot/mask, + /datum/strippable_item/mob_item_slot/neck, + /datum/strippable_item/mob_item_slot/eyes, + /datum/strippable_item/mob_item_slot/ears, + /datum/strippable_item/mob_item_slot/jumpsuit, + /datum/strippable_item/mob_item_slot/suit, + /datum/strippable_item/mob_item_slot/gloves, + /datum/strippable_item/mob_item_slot/feet, + /datum/strippable_item/mob_item_slot/suit_storage, + /datum/strippable_item/mob_item_slot/id, + /datum/strippable_item/mob_item_slot/belt, + /datum/strippable_item/mob_item_slot/pocket/left, + /datum/strippable_item/mob_item_slot/pocket/right, + /datum/strippable_item/hand/left, + /datum/strippable_item/hand/right, + /datum/strippable_item/mob_item_slot/handcuffs, + /datum/strippable_item/mob_item_slot/legcuffs, +))) + +/mob/living/carbon/human/proc/should_strip(mob/user) + if (user.pulling != src || user.grab_state != GRAB_AGGRESSIVE) + return TRUE + + if (ishuman(user)) + var/mob/living/carbon/human/human_user = user + return !human_user.can_be_firemanned(src) + + return TRUE + +/datum/strippable_item/mob_item_slot/eyes + key = STRIPPABLE_ITEM_EYES + item_slot = SLOT_GLASSES + +/datum/strippable_item/mob_item_slot/ears + key = STRIPPABLE_ITEM_EARS + item_slot = SLOT_EARS + +/datum/strippable_item/mob_item_slot/jumpsuit + key = STRIPPABLE_ITEM_JUMPSUIT + item_slot = SLOT_W_UNIFORM + +/datum/strippable_item/mob_item_slot/jumpsuit/get_alternate_action(atom/source, mob/user) + if(..() == FALSE) + return null + var/obj/item/clothing/under/jumpsuit = get_item(source) + if (!istype(jumpsuit)) + return null + return jumpsuit?.can_adjust ? "adjust_jumpsuit" : null + +/datum/strippable_item/mob_item_slot/jumpsuit/alternate_action(atom/source, mob/user) + if (!..()) + return null + var/obj/item/clothing/under/jumpsuit = get_item(source) + if (!istype(jumpsuit)) + return null + to_chat(source, "[user] is trying to adjust your [jumpsuit.name].") + if (!do_mob(user, source, jumpsuit.strip_delay * 0.5, ignorehelditem = TRUE)) + return + to_chat(source, "[user] successfully adjusted your [jumpsuit.name].") + jumpsuit.toggle_jumpsuit_adjust() + + if (!ismob(source)) + return null + + var/mob/mob_source = source + mob_source.update_inv_w_uniform() + mob_source.update_body() + return TRUE + +/datum/strippable_item/mob_item_slot/suit + key = STRIPPABLE_ITEM_SUIT + item_slot = SLOT_WEAR_SUIT + +/datum/strippable_item/mob_item_slot/suit/get_alternate_action(atom/source, mob/user) + if(..() == FALSE) + return null + var/obj/item/clothing/suit/space/hardsuit/suit = get_item(source) + if(istype(suit)) + if(!suit.helmettype) + return null + return suit?.suittoggled ? "disable_helmet" : "enable_helmet" + return null + +/datum/strippable_item/mob_item_slot/suit/alternate_action(mob/living/carbon/human/source, mob/user) + if(!..()) + return null + if(ishuman(source)) + var/obj/item/clothing/suit/space/hardsuit/hardsuit = get_item(source) + var/obj/item/clothing/head/helmet/space/hardsuit/hardsuit_head = hardsuit.helmet + source.visible_message("[user] tries to [hardsuit.suittoggled ? "retract" : "extend"] [source]'s helmet.", \ + "[user] tries to [hardsuit.suittoggled ? "retract" : "extend"] [source]'s helmet.", \ + target = user, target_message = "You try to [hardsuit.suittoggled ? "retract" : "extend"] [source]'s helmet.") + if(!do_mob(user, source, hardsuit_head ? hardsuit_head.strip_delay : POCKET_STRIP_DELAY, ignorehelditem = TRUE)) + return null + if((source.head != hardsuit_head) && source.head) + return null + if(hardsuit.ToggleHelmet(FALSE)) + source.visible_message("[user] [hardsuit_head ? "retract" : "extend"] [source]'s helmet", \ + "[user] [hardsuit_head ? "retract" : "extend"] [source]'s helmet", \ + target = user, target_message = "You [hardsuit_head ? "retract" : "extend"] [source]'s helmet.") + return TRUE + +/datum/strippable_item/mob_item_slot/gloves + key = STRIPPABLE_ITEM_GLOVES + item_slot = SLOT_GLOVES + +/datum/strippable_item/mob_item_slot/feet + key = STRIPPABLE_ITEM_FEET + item_slot = SLOT_SHOES + +/datum/strippable_item/mob_item_slot/feet/get_alternate_action(atom/source, mob/user) + if(..() == FALSE) + return null + var/obj/item/clothing/shoes/shoes = get_item(source) + if (!istype(shoes) || !shoes.can_be_tied) + return null + + switch (shoes.tied) + if (SHOES_UNTIED) + return "knot" + if (SHOES_TIED) + return "untie" + if (SHOES_KNOTTED) + return "unknot" + +/datum/strippable_item/mob_item_slot/feet/alternate_action(atom/source, mob/user) + if(!..()) + return null + var/obj/item/clothing/shoes/shoes = get_item(source) + if (!istype(shoes)) + return null + + shoes.handle_tying(user) + return TRUE + +/datum/strippable_item/mob_item_slot/suit_storage + key = STRIPPABLE_ITEM_SUIT_STORAGE + item_slot = SLOT_S_STORE + +/datum/strippable_item/mob_item_slot/suit_storage/get_alternate_action(atom/source, mob/user) + if(..() == FALSE) + return null + return get_strippable_alternate_action_internals(get_item(source), source) + +/datum/strippable_item/mob_item_slot/suit_storage/alternate_action(atom/source, mob/user) + if (!..()) + return null + return strippable_alternate_action_internals(get_item(source), source, user) + +/datum/strippable_item/mob_item_slot/id + key = STRIPPABLE_ITEM_ID + item_slot = SLOT_WEAR_ID + +/datum/strippable_item/mob_item_slot/belt + key = STRIPPABLE_ITEM_BELT + item_slot = SLOT_BELT + +/datum/strippable_item/mob_item_slot/belt/get_alternate_action(atom/source, mob/user) + if(..() == FALSE) + return null + return get_strippable_alternate_action_internals(get_item(source), source) + +/datum/strippable_item/mob_item_slot/belt/alternate_action(atom/source, mob/user) + if (!..()) + return null + return strippable_alternate_action_internals(get_item(source), source, user) + +/datum/strippable_item/mob_item_slot/pocket + /// Which pocket we're referencing. Used for visible text. + var/pocket_side + +/datum/strippable_item/mob_item_slot/pocket/get_obscuring(atom/source) + return isnull(get_item(source)) \ + ? STRIPPABLE_OBSCURING_NONE \ + : STRIPPABLE_OBSCURING_HIDDEN + +/datum/strippable_item/mob_item_slot/pocket/get_equip_delay(obj/item/equipping) + return POCKET_EQUIP_DELAY + +/datum/strippable_item/mob_item_slot/pocket/start_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + warn_owner(source) + +/datum/strippable_item/mob_item_slot/pocket/start_unequip(atom/source, mob/user) + var/obj/item/item = get_item(source) + if (isnull(item)) + return FALSE + + to_chat(user, span_notice("You try to empty [source]'s [pocket_side] pocket.")) + + var/log_message = "[key_name(source)] is being pickpocketed of [item] by [key_name(user)] ([pocket_side])" + user.log_message(log_message, LOG_ATTACK, color="red") + source.log_message(log_message, LOG_VICTIM, color="red", log_globally=FALSE) + item.add_fingerprint(source) + + var/result = start_unequip_mob(item, source, user, POCKET_STRIP_DELAY) + + if (!result) + warn_owner(source) + + return result + +/datum/strippable_item/mob_item_slot/pocket/proc/warn_owner(atom/owner) + to_chat(owner, span_warning("You feel your [pocket_side] pocket being fumbled with!")) + +/datum/strippable_item/mob_item_slot/pocket/left + key = STRIPPABLE_ITEM_LPOCKET + item_slot = SLOT_L_STORE + pocket_side = "left" + +/datum/strippable_item/mob_item_slot/pocket/right + key = STRIPPABLE_ITEM_RPOCKET + item_slot = SLOT_R_STORE + pocket_side = "right" + +/proc/get_strippable_alternate_action_internals(obj/item/item, atom/source) + if (!iscarbon(source)) + return null + + var/mob/living/carbon/carbon_source = source + var/obj/item/clothing/mask + var/internals = FALSE + + for(mask in GET_INTERNAL_SLOTS(carbon_source)) + if(istype(mask, /obj/item/clothing/mask)) + var/obj/item/clothing/mask/M = mask + if(M.mask_adjusted) + if(M.adjustmask(carbon_source)) + internals = TRUE + else + internals = TRUE + if((mask.clothing_flags & ALLOWINTERNALS)) + internals = TRUE + + if(carbon_source.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + internals = TRUE + + if (internals && istype(item, /obj/item/tank)) + return isnull(carbon_source.internal) ? "enable_internals" : "disable_internals" + +/proc/strippable_alternate_action_internals(obj/item/item, atom/source, mob/user) + var/obj/item/tank/tank = item + if (!istype(tank)) + return null + + var/mob/living/carbon/carbon_source = source + if (!istype(carbon_source)) + return null + + var/obj/item/clothing/mask + var/internals = FALSE + + for(mask in GET_INTERNAL_SLOTS(carbon_source)) + if(istype(mask, /obj/item/clothing/mask)) + var/obj/item/clothing/mask/M = mask + if(M.mask_adjusted) + if(M.adjustmask(carbon_source)) + internals = TRUE + else + internals = TRUE + if((mask.clothing_flags & ALLOWINTERNALS)) + internals = TRUE + + if(!internals) + return null + + carbon_source.visible_message( + span_danger("[user] tries to [isnull(carbon_source.internal) ? "open": "close"] the valve on [source]'s [item.name]."), + span_userdanger("[user] tries to [isnull(carbon_source.internal) ? "open": "close"] the valve on your [item.name]."), + ignored_mobs = user, + ) + + to_chat(user, span_notice("You try to [isnull(carbon_source.internal) ? "open": "close"] the valve on [source]'s [item.name]...")) + + if(!do_mob(user, carbon_source, INTERNALS_TOGGLE_DELAY, ignorehelditem = TRUE)) + return null + + if(carbon_source.internal) + carbon_source.internal = null + + // This isn't meant to be FALSE, it correlates to the icon's name. + carbon_source.update_internals_hud_icon(0) + else if (!QDELETED(item)) + if(internals || carbon_source.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + carbon_source.internal = item + carbon_source.update_internals_hud_icon(1) + + carbon_source.visible_message( + span_danger("[user] [isnull(carbon_source.internal) ? "closes": "opens"] the valve on [source]'s [item.name]."), + span_userdanger("[user] [isnull(carbon_source.internal) ? "closes": "opens"] the valve on your [item.name]."), + ignored_mobs = user, + ) + + to_chat(user, span_notice("You [isnull(carbon_source.internal) ? "close" : "open"] the valve on [source]'s [item.name].")) + + return TRUE + +#undef INTERNALS_TOGGLE_DELAY +#undef POCKET_EQUIP_DELAY diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm index 4ab24c31dd..f8ac8c6716 100644 --- a/code/modules/mob/living/carbon/monkey/monkey.dm +++ b/code/modules/mob/living/carbon/monkey/monkey.dm @@ -16,6 +16,17 @@ /obj/item/bodypart/r_arm/monkey, /obj/item/bodypart/r_leg/monkey, /obj/item/bodypart/l_leg/monkey) hud_type = /datum/hud/monkey +GLOBAL_LIST_INIT(strippable_monkey_items, create_strippable_list(list( + /datum/strippable_item/mob_item_slot/head, + /datum/strippable_item/mob_item_slot/back, + /datum/strippable_item/mob_item_slot/mask, + /datum/strippable_item/mob_item_slot/neck, + /datum/strippable_item/hand/left, + /datum/strippable_item/hand/right, + /datum/strippable_item/mob_item_slot/handcuffs, + /datum/strippable_item/mob_item_slot/legcuffs, +))) + /mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner) add_verb(src, /mob/living/proc/mob_sleep) add_verb(src, /mob/living/proc/lay_down) @@ -47,6 +58,7 @@ . = ..() AddElement(/datum/element/mob_holder, worn_state = "monkey", inv_slots = ITEM_SLOT_HEAD) AddComponent(/datum/component/footstep, FOOTSTEP_MOB_BAREFOOT, 1, 2) + AddElement(/datum/element/strippable, GLOB.strippable_monkey_items) /mob/living/carbon/monkey/Destroy() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 2488fe5127..28fd4e0451 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -935,13 +935,6 @@ what.forceMove(drop_location()) log_combat(src, who, "stripped [what] off") - if(Adjacent(who)) //update inventory window - who.show_inv(src) - else - src << browse(null,"window=mob[REF(who)]") - - who.update_equipment_speed_mods() // Updates speed in case stripped speed affecting item - // The src mob is trying to place an item on someone // Override if a certain mob should be behave differently when placing items (can't, for example) /mob/living/stripPanelEquip(obj/item/what, mob/who, where) diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm index fb3d009a72..e8c29e6d55 100644 --- a/code/modules/mob/living/simple_animal/friendly/dog.dm +++ b/code/modules/mob/living/simple_animal/friendly/dog.dm @@ -24,6 +24,7 @@ . = ..() AddElement(/datum/element/wuv, "yaps happily!", EMOTE_AUDIBLE, /datum/mood_event/pet_animal, "growls!", EMOTE_AUDIBLE) AddElement(/datum/element/mob_holder, held_icon) + AddElement(/datum/element/strippable, GLOB.strippable_corgi_items) //Corgis and pugs are now under one dog subtype @@ -104,18 +105,175 @@ ..(gibbed) regenerate_icons() -/mob/living/simple_animal/pet/dog/corgi/show_inv(mob/user) - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) +GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list( + /datum/strippable_item/corgi_head, + /datum/strippable_item/corgi_back, + /datum/strippable_item/corgi_collar, + /datum/strippable_item/corgi_id, +))) + +/datum/strippable_item/corgi_head + key = STRIPPABLE_ITEM_HEAD + +/datum/strippable_item/corgi_head/get_item(atom/source) + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) return - user.set_machine(src) - var/dat = "
Inventory of [name]

" - dat += "
Head: [inventory_head]" : "add_inv=head'>Nothing"]" - dat += "
Back: [inventory_back]" : "add_inv=back'>Nothing"]" - dat += "
Collar: [pcollar]" : "add_inv=collar'>Nothing"]" + return corgi_source.inventory_head - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "mob[REF(src)]") +/datum/strippable_item/corgi_head/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return FALSE + + corgi_source.place_on_head(equipping, user) + +/datum/strippable_item/corgi_head/finish_unequip(atom/source, mob/user) + ..() + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + finish_unequip_mob(corgi_source.inventory_head, corgi_source, user) + corgi_source.inventory_head = null + corgi_source.update_corgi_fluff() + corgi_source.regenerate_icons() + +/datum/strippable_item/corgi_back + key = STRIPPABLE_ITEM_BACK + +/datum/strippable_item/corgi_back/get_item(atom/source) + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + return corgi_source.inventory_back + +/datum/strippable_item/corgi_back/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE + + if (!ispath(equipping.dog_fashion, /datum/dog_fashion/back)) + to_chat(user, "You set [equipping] on [source]'s back, but it falls off!") + equipping.forceMove(source.drop_location()) + if (prob(25)) + step_rand(equipping) + dance_rotate(source, set_original_dir = TRUE) + + return FALSE + + return TRUE + +/datum/strippable_item/corgi_back/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return FALSE + + equipping.forceMove(corgi_source) + corgi_source.inventory_back = equipping + corgi_source.update_corgi_fluff() + corgi_source.regenerate_icons() + +/datum/strippable_item/corgi_back/finish_unequip(atom/source, mob/user) + ..() + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + finish_unequip_mob(corgi_source.inventory_back, corgi_source, user) + corgi_source.inventory_back = null + corgi_source.update_corgi_fluff() + corgi_source.regenerate_icons() + +/datum/strippable_item/corgi_collar + key = STRIPPABLE_ITEM_CORGI_COLLAR + +/datum/strippable_item/corgi_collar/get_item(atom/source) + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + return corgi_source.pcollar + +/datum/strippable_item/corgi_collar/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE + + if (!istype(equipping, /obj/item/clothing/neck/petcollar)) + to_chat(user, "That's not a collar.") + return FALSE + + return TRUE + +/datum/strippable_item/corgi_collar/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return FALSE + + corgi_source.add_collar(equipping, user) + corgi_source.update_corgi_fluff() + +/datum/strippable_item/corgi_collar/finish_unequip(atom/source, mob/user) + ..() + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + finish_unequip_mob(corgi_source.pcollar, corgi_source, user) + corgi_source.pcollar = null + corgi_source.update_corgi_fluff() + corgi_source.regenerate_icons() + +/datum/strippable_item/corgi_id + key = STRIPPABLE_ITEM_ID + +/datum/strippable_item/corgi_id/get_item(atom/source) + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + return corgi_source.access_card + +/datum/strippable_item/corgi_id/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE + + if (!istype(equipping, /obj/item/card/id)) + to_chat(user, "You can't pin [equipping] to [source]!") + return FALSE + + return TRUE + +/datum/strippable_item/corgi_id/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return FALSE + + equipping.forceMove(source) + corgi_source.access_card = equipping + +/datum/strippable_item/corgi_id/finish_unequip(atom/source, mob/user) + ..() + var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source + if (!istype(corgi_source)) + return + + finish_unequip_mob(corgi_source.access_card, corgi_source, user) + corgi_source.access_card = null + corgi_source.update_corgi_fluff() + corgi_source.regenerate_icons() /mob/living/simple_animal/pet/dog/corgi/getarmor(def_zone, type) var/armorval = 0 @@ -158,114 +316,12 @@ ..() update_corgi_fluff() -/mob/living/simple_animal/pet/dog/corgi/Topic(href, href_list) - if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=mob[REF(src)]") - usr.unset_machine() - return - - //Removing from inventory - if(href_list["remove_inv"]) - var/remove_from = href_list["remove_inv"] - switch(remove_from) - if(BODY_ZONE_HEAD) - if(inventory_head) - usr.put_in_hands(inventory_head) - inventory_head = null - update_corgi_fluff() - regenerate_icons() - else - to_chat(usr, "There is nothing to remove from its [remove_from].") - return - if("back") - if(inventory_back) - usr.put_in_hands(inventory_back) - inventory_back = null - update_corgi_fluff() - regenerate_icons() - else - to_chat(usr, "There is nothing to remove from its [remove_from].") - return - if("collar") - if(pcollar) - usr.put_in_hands(pcollar) - pcollar = null - update_corgi_fluff() - regenerate_icons() - - show_inv(usr) - - //Adding things to inventory - else if(href_list["add_inv"]) - - var/add_to = href_list["add_inv"] - - switch(add_to) - if("collar") - var/obj/item/clothing/neck/petcollar/P = usr.get_active_held_item() - if(!istype(P)) - to_chat(usr,"That's not a collar.") - return - add_collar(P, usr) - update_corgi_fluff() - - if(BODY_ZONE_HEAD) - place_on_head(usr.get_active_held_item(),usr) - - if("back") - if(inventory_back) - to_chat(usr, "It's already wearing something!") - return - else - var/obj/item/item_to_add = usr.get_active_held_item() - - if(!item_to_add) - usr.visible_message("[usr] pets [src].","You rest your hand on [src]'s back for a moment.") - return - - if(!usr.temporarilyRemoveItemFromInventory(item_to_add)) - to_chat(usr, "\The [item_to_add] is stuck to your hand, you cannot put it on [src]'s back!") - return - - if(istype(item_to_add, /obj/item/grenade/plastic)) // last thing he ever wears, I guess - item_to_add.afterattack(src,usr,1) - return - - //The objects that corgis can wear on their backs. - var/allowed = FALSE - if(ispath(item_to_add.dog_fashion, /datum/dog_fashion/back)) - allowed = TRUE - - if(!allowed) - to_chat(usr, "You set [item_to_add] on [src]'s back, but it falls off!") - item_to_add.forceMove(drop_location()) - if(prob(25)) - step_rand(item_to_add) - for(var/i in list(1,2,4,8,4,8,4,dir)) - setDir(i) - sleep(1) - return - - item_to_add.forceMove(src) - src.inventory_back = item_to_add - update_corgi_fluff() - regenerate_icons() - - show_inv(usr) - else - return ..() - //Corgis are supposed to be simpler, so only a select few objects can actually be put //to be compatible with them. The objects are below. //Many hats added, Some will probably be removed, just want to see which ones are popular. // > some will probably be removed /mob/living/simple_animal/pet/dog/corgi/proc/place_on_head(obj/item/item_to_add, mob/user) - - if(istype(item_to_add, /obj/item/grenade/plastic)) // last thing he ever wears, I guess - INVOKE_ASYNC(item_to_add, /obj/item.proc/afterattack, src, user, 1) - return - if(inventory_head) if(user) to_chat(user, "You can't put more than one hat on [src]!") @@ -303,9 +359,7 @@ item_to_add.forceMove(drop_location()) if(prob(25)) step_rand(item_to_add) - for(var/i in list(1,2,4,8,4,8,4,dir)) - setDir(i) - sleep(1) + dance_rotate(src, set_original_dir = TRUE) return valid diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index bd45c482a2..7c7a684cf3 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -124,6 +124,9 @@ /mob/living/simple_animal/parrot/proc/toggle_mode, /mob/living/simple_animal/parrot/proc/perch_mob_player)) +/mob/living/simple_animal/parrot/ComponentInitialize() + . = ..() + AddElement(/datum/element/strippable, GLOB.strippable_parrot_items) /mob/living/simple_animal/parrot/examine(mob/user) . = ..() @@ -183,91 +186,101 @@ return 0 -/* - * Inventory - */ -/mob/living/simple_animal/parrot/show_inv(mob/user) - user.set_machine(src) +GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( + /datum/strippable_item/parrot_headset, +))) - var/dat = "

Inventory of [name]

" - dat += "
Headset: [ears]" : "add_inv=ears'>Nothing"]" +/datum/strippable_item/parrot_headset + key = STRIPPABLE_ITEM_PARROT_HEADSET - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "window=mob[REF(src)]") +/datum/strippable_item/parrot_headset/get_item(atom/source) + var/mob/living/simple_animal/parrot/parrot_source = source + return istype(parrot_source) ? parrot_source.ears : null +/datum/strippable_item/parrot_headset/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE -/mob/living/simple_animal/parrot/Topic(href, href_list) - if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=mob[REF(src)]") - usr.unset_machine() + if (!istype(equipping, /obj/item/radio/headset)) + to_chat(user, "[equipping] won't fit!") + return FALSE + + return TRUE + +// There is no delay for putting a headset on a parrot. +/datum/strippable_item/parrot_headset/start_equip(atom/source, obj/item/equipping, mob/user) + if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY) + return FALSE + return TRUE + +/datum/strippable_item/parrot_headset/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + var/obj/item/radio/headset/radio = equipping + if (!istype(radio)) + return FALSE + + var/mob/living/simple_animal/parrot/parrot_source = source + if (!istype(parrot_source)) + return FALSE + + if (!user.transferItemToLoc(radio, source)) + return FALSE + + parrot_source.ears = radio + + to_chat(user, "You fit [radio] onto [source].") + + parrot_source.available_channels.Cut() + + for (var/channel in radio.channels) + var/channel_to_add + + switch (channel) + if (RADIO_CHANNEL_ENGINEERING) + channel_to_add = RADIO_TOKEN_ENGINEERING + if (RADIO_CHANNEL_COMMAND) + channel_to_add = RADIO_TOKEN_COMMAND + if (RADIO_CHANNEL_SECURITY) + channel_to_add = RADIO_TOKEN_SECURITY + if (RADIO_CHANNEL_SCIENCE) + channel_to_add = RADIO_TOKEN_SCIENCE + if (RADIO_CHANNEL_MEDICAL) + channel_to_add = RADIO_TOKEN_MEDICAL + if (RADIO_CHANNEL_SUPPLY) + channel_to_add = RADIO_TOKEN_SUPPLY + if (RADIO_CHANNEL_SERVICE) + channel_to_add = RADIO_TOKEN_SERVICE + + if (channel_to_add) + parrot_source.available_channels += channel_to_add + + if (radio.translate_binary) + parrot_source.available_channels.Add(MODE_TOKEN_BINARY) + +/datum/strippable_item/parrot_headset/start_unequip(atom/source, mob/user) + . = ..() + if (!.) + return FALSE + + var/mob/living/simple_animal/parrot/parrot_source = source + if (!istype(parrot_source)) return - //Removing from inventory - if(href_list["remove_inv"]) - var/remove_from = href_list["remove_inv"] - switch(remove_from) - if("ears") - if(!ears) - to_chat(usr, "There is nothing to remove from its [remove_from]!") - return - if(!stat) - say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - ears.forceMove(drop_location()) - ears = null - for(var/possible_phrase in speak) - if(copytext_char(possible_phrase, 2, 3) in GLOB.department_radio_keys) - possible_phrase = copytext_char(possible_phrase, 3) + if (!parrot_source.stat) + parrot_source.say("[parrot_source.available_channels.len ? "[pick(parrot_source.available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - //Adding things to inventory - else if(href_list["add_inv"]) - var/add_to = href_list["add_inv"] - if(!usr.get_active_held_item()) - to_chat(usr, "You have nothing in your hand to put on its [add_to]!") - return - switch(add_to) - if("ears") - if(ears) - to_chat(usr, "It's already wearing something!") - return - else - var/obj/item/item_to_add = usr.get_active_held_item() - if(!item_to_add) - return + return TRUE - if( !istype(item_to_add, /obj/item/radio/headset) ) - to_chat(usr, "This object won't fit!") - return - - var/obj/item/radio/headset/headset_to_add = item_to_add - - if(!usr.transferItemToLoc(headset_to_add, src)) - return - ears = headset_to_add - to_chat(usr, "You fit the headset onto [src].") - - clearlist(available_channels) - for(var/ch in headset_to_add.channels) - switch(ch) - if(RADIO_CHANNEL_ENGINEERING) - available_channels.Add(RADIO_TOKEN_ENGINEERING) - if(RADIO_CHANNEL_COMMAND) - available_channels.Add(RADIO_TOKEN_COMMAND) - if(RADIO_CHANNEL_SECURITY) - available_channels.Add(RADIO_TOKEN_SECURITY) - if(RADIO_CHANNEL_SCIENCE) - available_channels.Add(RADIO_TOKEN_SCIENCE) - if(RADIO_CHANNEL_MEDICAL) - available_channels.Add(RADIO_TOKEN_MEDICAL) - if(RADIO_CHANNEL_SUPPLY) - available_channels.Add(RADIO_TOKEN_SUPPLY) - if(RADIO_CHANNEL_SERVICE) - available_channels.Add(RADIO_TOKEN_SERVICE) - - if(headset_to_add.translate_binary) - available_channels.Add(MODE_TOKEN_BINARY) - else - return ..() +/datum/strippable_item/parrot_headset/finish_unequip(atom/source, mob/user) + ..() + var/mob/living/simple_animal/parrot/parrot_source = source + if (!istype(parrot_source)) + return + finish_unequip_mob(parrot_source.ears, parrot_source, user) + parrot_source.ears = null /* * Attack responces diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 8a71bb72c4..d240f97a23 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -289,9 +289,6 @@ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE, A) return TRUE -/mob/proc/show_inv(mob/user) - return - //view() but with a signal, to allow blacklisting some of the otherwise visible atoms. /mob/proc/fov_view(dist = world.view) . = view(dist, src) @@ -508,10 +505,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) unset_machine() src << browse(null, t1) - if(href_list["refresh"]) - if(machine && in_range(src, usr)) - show_inv(machine) - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) if(href_list["item"]) var/slot = text2num(href_list["item"]) @@ -528,12 +521,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) else usr.stripPanelEquip(what,src,slot) - if(usr.machine == src) - if(Adjacent(usr)) - show_inv(usr) - else - usr << browse(null,"window=mob[REF(src)]") - // The src mob is trying to strip an item from someone // Defined in living.dm /mob/proc/stripPanelUnequip(obj/item/what, mob/who) @@ -555,12 +542,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) if(isAI(M)) return -/mob/MouseDrop_T(atom/dropping, atom/user) - . = ..() - if(ismob(dropping) && dropping != user) - var/mob/M = dropping - M.show_inv(user) - /mob/proc/is_muzzled() return FALSE diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index eab1c55ca9..75cf6594e6 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -1010,7 +1010,7 @@ GLOBAL_LIST_EMPTY(vending_products) if(base64_cache[T.type]) base64 = base64_cache[T.type] else - base64 = icon2base64(icon(T.icon, T.icon_state)) + base64 = icon2base64(icon(T.icon, T.icon_state, SOUTH, 1)) base64_cache[T.type] = base64 break var/list/data = list( diff --git a/icons/UI_Icons/inventory/back.png b/icons/UI_Icons/inventory/back.png new file mode 100644 index 0000000000000000000000000000000000000000..736b9d64bf997fe7d6874db93e079c83b187ebdc GIT binary patch literal 1796 zcmbVNe{2&~9KSMfimXGNBy7{n%z2oji zyN=<)U^xB|L~scZ!em4UlVC(8BLWKO3?fl7s!q+Kk)RO4I2~h4@Lkt7peT{c-Mx43 zectE$`+MD*}V%TP&qCODdLpJQjiBlNLb2e;cv72;DcF@FE zMrBYNt@5(bX4cAKOBSPxA~YlrfXbi|AsABVhz*PL(r~UV6BrtYsLeL4OcRLKyFI8~ zlmTiskT^?{6k1|67|lgR7E2*YkrYXgC4`a0DT*e|bg>CdTo{xlbAGzUu{0qIe%Y`l zRh4Lh2#3Rlu*o3G0m5jtS_zUOC<=!NTxkudOau=pc}WHbP*|ClR9+0B8YANqTT~kc zHJvg+kW#cECE+IMFe1W8gwa51CdGjqo5D#gaxgBOV+jxh0tl%J#2QmrsYz5trAho3 z>eTi<0-$T%?v#xC>LLg!5sK57>{9OltgHeGEgHbc$qzN}#y<{R;XWT*f?J9~5oakjvSx}(o_C0Bin1exGN^Js;s~EZY#6J>A8=S=nSW>xOwop$03`j7yqJP1qF)U&GAIi`Z#^*Y z2&g=v73`E9#NSPf@7-10NjU5n+BDUw;76+2fe`F)GHhjM?&QM`soUXllzHpV4E0=l zv9omcruNU@>s~RKbIg6=z>ad_*sG45!396j+&-NzfRv>o)f7p4hsfxBK`rA3&<_meuKVpDuml?VK>* z8+)^Bj{mpSoA=A(>*ubTw)ClvBP&+V7@X9dR&ZonbNT#U-PH-DKW)$t27XWFeebTcbvF1+{EzN z#W%h0?;BrMm-c(ZP=;{o$yYKw19_9t-36D-=GXcwZj9d9qkn5jW?R;$J*!(swEvXL LS>-rV-nii}bGmM* literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/belt.png b/icons/UI_Icons/inventory/belt.png new file mode 100644 index 0000000000000000000000000000000000000000..1be89d450a8f4d38ccf9a37072687406042faa4b GIT binary patch literal 1596 zcmbVMU1%Id9KRgbP}4T}P%T)f)2+0Aba!X>lDq8CYfSI1cURpQ(t{+THgmgkcS~+} zw!53|-BGA%sZvDr!GNL#E#iYIRuPHfgTX3=RxuB1C4zz$9}2}h2$9yJv-d@+)nb>O z-TC;<@BjTjF*2Ol&~r~uC=}X|%ce%z9|U7ZA4M891+3Eo5vr!m(4o+-!K#h5DdK?wDH&FR`}N~bIAG`rZmeI0s+}Zd zBU^XK{`zoUt50c)&JFGcyQ+v0n8d@NYE~>4RTEr;7qNM;Epng%@um{oP#_SDt0N#u z9Rgwkl+6*b;YK~!h2DL!IhzoSuOK?onrU|Cq zp|#wWo0!AIDz-&QfPqO3pssar_Ow%J2-h`{RES9|&t+JtgSE@lqi&i03w7uC1_8{q zs@jopQ(a86Bf|C4hnN{HhujqH=4&<)M~O?P9gU<9F*UaZ*4QZN5bRMWPidtcsFAkH zK$Zmv?jJKOo%-$rt1gff_DF(bQImKW1Yr}ZQr#%l{sCnHX0H^x z0m@QF#~!{VSl3XIIwocY8zwFh(Y8t)=(rIjX@xpWFw-u&dOw#;jySYvRM>$#n%M(# z>10e&VliG8q=vexigK3gVM`;qRDxs95e!2|FdiiZy-(w%Sd8$R+*jbSrWN@30Erf* zf}|@^+`gZp+H{Zt?fd%wy#JVEuoA?T+vW)hGsqp3HCz_eTB~&SlY^~U#Q=?TL0AjQ zAi-%t{E5!BwvF5NK$F%l6Gr+UdC`QqwCMTRAwwnRty|`u$dng@f^FI%{@ujJ*{b4h z!&%1&rkz&B7M*5CEY{&1*2*HuxffU){34ec%8#GDGW&hcr*Qo<*G^r2>-Y6rE`Qnm zLv82LGnXDYz7$#d{G*?WB9b=UTY2%1!xyr1r$2ab$G%AF;o!ZxQN5UX*&IH$u;sqo z!n6F!%DMT;KR;ZveJ(k5xOD2fr(Rzi3m^Msc4lj}>%2X&d;a>|#^V>aFI>5Bvg=a% z>2LPp@Ee1p*EWY=n?HEu&gHM3yV||>%vt}g_kTWcQl44-(GO2V3NI}^|KzK0^2^sH z^zFM7na9(a^UC7s$(0kY9GLvtpLyaO2f|0TE=P9su3NY1y?t*7{XLf+PQ9~d^5|b_ CA`k5V literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/collar.png b/icons/UI_Icons/inventory/collar.png new file mode 100644 index 0000000000000000000000000000000000000000..71803b1b6c6b535c4a0d706240fdd4da64227962 GIT binary patch literal 2090 zcmbVNYiQk69FH3t+q#WCzH|d6q(eHkb93^(iM{LE-f1h|u5`s!aj=td(wMnMxhYZbeJ-yWs;R%xPHYs{RPWb)8=K}9JIpLsIfQ4{?l)S;& zh#Z(59>cQ}*mQ*6J$z5qVgx=(5MTAnL2OlXLXFpA>-4oK@HI#>krVn-hx~Y9gdd<0 z;q?pz7($sh%#5U~YBsx@m!S+rXowO7vTQ-!(iFaN2+UjLxYlU?(S|SfloLux5?Z2I zsZ=r*B}1d4D4C`yLRpk$zz`sw2@+HVLEP13$dedHUYK|^;8R9q)2SpUFs1bn{IEqE z#Emqugo#xYic$uqA=Q8mZsEeIs9baIV3CxGPl6<7SgC~#OEjTzi7rFk`h152mfAw0 z$H1iH^r+LL!cmm`+8Q?3-pXyV7VvYak*hq0tznPr&#`P- zCRtb2fI~D4P*!CZL=ccQ*+s~Wo-(9E&Zt2Se3m!O?t_8A#|YwbYiH$HdV%KWL+RBs;+37Y3MTP z5!2CNM)j#{vsSQ9Y-SJKtZoCFs5US)HfULe5JQn|?CM&Zb&aPkL%h3QCXZEV&`b5t zH2fYQlPv{&t-~#Z)5*;VIBi+t2#wd?64kx0JT~krCBjJW(#m!3F?Ev)ib!9Pb;3PI zMO;=q4W>!dCO-BWab z15l5B$rQDZ@c&6sBmKC9f+AttjVPqS)Vii#eDS}1QTx{R6K}%V&X6u!TM~O{ZE7T7 z)DhdVw(MGQhHZCOhw^=6yeGe;QIX0YyO)9-@bEw<78Lx#<}rTyFM!ZvH9iK z?d@le8Mn}m%iWd7AHH~f#V3pVZ|r{P`v(`!ym)Hm=}U{-_!F<9^H)k2x$>1SxEp;V zA8uaM*3ZpvS=8ibj+c5?o+j6Ba^aWn8J7>u&ENDU&s{kE{?Vs@y128G|Mc+MBTxSP zcL#Uk!q&aLA9Kg)^V?pz`gLy22X7bVPHvjJ+W*6fPT1SMi9d4c(BN;Yy8klPL1)*( yFMEG{u5)e&n0+m?fVjo4PAz`(k224_`&q|Xqw?Le{FQV!AKEvZKi_}&g?|BkKCR0D literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/ears.png b/icons/UI_Icons/inventory/ears.png new file mode 100644 index 0000000000000000000000000000000000000000..e9a8f3c23c4bf20a2269121cef3797d04f616ea4 GIT binary patch literal 1688 zcmbVNZEVzJ9PcUU#vJM(f=**8L588Pch|eC6t-RG?vmZSjgxJJ59`|JcHORhy0+Wh zEg@`EjKn3z@L`KAX2v)IALdI)kT739D8wicC5VX<2bc&L!xw{LbLi9C-DN0Bq)Gd{ z{hr_d{oj4@=ElkukFCHktTGyrTG5ERZ`pn5YYz_ohK6!8(qUs*#VYspV6RWC#jxd% zXo+^GJ=P>BP^V-Sb^$7@n+T0zTef9QS?L81-UYffLnN*p|CqovRV3Q{F(zh)K}w4Z zT44KNb3z&HRYEGUZ7aSdD~};0X}V~F zZkA{byWl3|Fgh!nG)pnANqL~ElyGLhmCg%S6&j?04h+XeShj>UQ_z8S3jPaq>G&1_ z$hEOpNycq;(e;uD+i4g;W)vK9TeO|XnSgEuHte?)&@h11T<=@RA#aFib-W2}as^m+nWy;kX5pS{fbL zt&R0~v?0v1A)Y5Wip{GViwRM~c4R{VQAs3_b0|$y1%I+j<$}OZ@@kSN1FA1b23em< zvT}e)@;-)Vyh`!D1eJa_1&a68|9Ss8OG70nr|+7_Elf9egotLNsOAc#vmNvmW@!!2 zr;8vfZW)M#;>I7SL}6RIYY!~aW>Nr={zqOc!fcpyGO`8gx{g6By>HiAr^e_PyuFVk<|OhxQ&DS+lxj z=7l}g(%8)xBhCeKX4zQJyKjviQiY~cHsN6W#$z+O=W$*FS|2#i*{JkHsA7rWS2z$MH v_WAn{i(d{$uIGBO)xU2{Z*4o*iM{d5mDXP$-o54{_umn1XqKkyJ4gNk667+U literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/glasses.png b/icons/UI_Icons/inventory/glasses.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6f1ad098f6d52ccdd7001ae68b001ad0e497c9 GIT binary patch literal 2017 zcmb_dZD<@t7~Y7fHZc@z3&kIH-6(0r-JRXrZ*sQjT}%$W7?W#r{)l#VclK`0?MHSu zxyxZO7AuPSLlmWL8c-A}QjJLQM=1td3kpRLwD|pjC@3acmDYdGUha;<0xr(l(RSh?oUgKZS~R ze>FhE)q$K*9Wzvu+IA<|UD2=s2St#qI3+jKDoLtlSHtbNo2AGaBpORnJ+VVFpB*Gq zUVuo60ki=CPs%F8NrIp#caS{b0Sjc712oTTK+?nnS-&XkEif%@D1BGm7ye69#VGPM zmMxddOgX`LL4oB|Rb>Is@;r?ZbU5WkutK|GXTw4og+^fek?px;Y!T|-M3kg3)1?p` zziHMD>uJIXV=K^SIR?Zb)qtka#Q76Jspi}?SX4p|a-$GqxhB>xdXX0vy;V#%hyUdO zr#72y`dE>R<1}4_QST%Uqn?lz(P3`NN9+&^y@|j;y_1+`XB>^Mr2+&aFUWabsZprG z24#}x89;6sv0c+Ehg+9TKxr7EB!#QS(I7!{Y7U4Rr)mj>2C@dg5;W_Xwl#GT6c?Bi zH383D2E~#wVFWJ=HVw`40te!NZ3h+*>$?StY^G65c_lBvjxoE$^8QRJH5ho7UBUz5 zP~Q$R)0>hwRg!3);c9fVSuNv+5p)fdNhc|s9LBay4IvIDWI?BeL_()k9rIQ!L8WED zLzxpe(-efp{Iq9G#1d%CH}QN`=M`R2O|!pTchM@W)+QB`P37CAa03No#m7NlhZCPYOxfu!h- z&@ZAMZ$v;7g@#VAhI&1%MiIINg!fdI`Y*v;i4s?-!-b3P{|knVx6&nru@{Q3Hnwcr z#f7YO;k`U=HV+Z}(>#Wdi}MuVLuQ@&(EIp+I+IEFy^_53)m=Mxe#*X;pKgus`Av_KTW^`! z{LPlzPCpKg{IT_u*$p4flFpg2*57n<=BtI#^W!ZK%-jtYUhnMx@sVo}zqB|{s?%@I z=et0DrsemOH-A~|8h@Eq-hSrfthM`vX}49W-hS%*!HrMv8rgop=v#Z^)bWK=8@lgX z9R1;l_2IeEuaExp-Pw=#ztc4a`iSw~f9V|`JpB2I2f6k+qJ3`q*=yEq-n*yA TGWHM0hj6BMApORU-FyE5goTfO literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/gloves.png b/icons/UI_Icons/inventory/gloves.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8a16cbdb7afb31597ca4d51d3fd42a97b80d74 GIT binary patch literal 2107 zcmb_d3uxSA98c|R_o^+5vvh)4hT!Ise97gKOYHTrySkq9YUz%yL#oRq-`$1XC0Ua8 z?m8!>>|qhg7&4hC)+gJ9c4Z*UDUQM_)m1i;DN|vJ&RT{WGMQE?e!1Qq-J{zm29kV_ z-}is~{*SM}wPi!a(we0hhE;@`gKg-KyYJ#M^tF3>uA<)(vw4e+VapzH-xBP#*Q+tC z?5Gy$a5}=KHoZ=1uUR}Kr?`3Sqbox*MsLD0%^03_D@WBf7YK zodQH{I9$|mOIuQ@q6*t->PBwl3ArWOj$}}bF8a7ldmc0pz z0?hCd5Uzuwlu>0zz9U#wq?lo)WaO}xlH-sz^*DhSX_Nv+(y)+XWSwX3dMFTRwTzgS zL>qS7hV^);DZn!#&yy_0Z3pxP|HFj?}E#D5}|f>1=}A^P{AO=h8)z6}JrhgyO~@sziQSy9EPx=fSMl zbOIuwx59)vXt$NlW*B{A z^5ypCv|SX)+!E^L?yz`?ph@u>f;!^KLYK^mYZtCzSP2sfHby#j|L|(~zCKX3`}msr z{=E$=PL7g&D~Er6ujX{~p08yoH8M5%)1&WlJD-<^1LGI7Ump5nI@xe)JNMV=iH)ne zuY7=Q?eTRQ5%6z22F!!+e6F*DuK)=f9NRxqdGSGf@-Tsp8 z0>QpjieIgM>d5qX>q}MpCK^I3_Aftt*x&x=L#1nL{ywyJ(d6XYU#uUj-ghRMEII${ z!zTxNs|N0|-ls5Xu2&GflmG(=Lwd2F*YWLjVJ3KSJYbJPU*A#YcIrrn((B*RH>n6_(Xr%h0)*s$)0-i`+HwFQ*M^8zO zde>8QOq}Rnr4)w6KQ=@+6zULVOefMQisBy%vmopbhXa{EvHnCY`f|NHx~<(tF_65M z_deg>zn&W!?BCRWTRX!rn^FVGVY;%xyD>z+UbXrwU9NKm9`YEbkocrmW_c+!v;^oO2tee*t2%xS_ zr<*cX)umW$itzlt32H{&A*-Uj%%p?GVeFBTYv8^Ks^*r!8V4p^jC|r|2q`oIHPld< zm1P02_w2E3la#$}D+q8B`8dwes7X9fcuC8E2$VD!(RdJr04zh(#I&-Lmq2NNB^4^* z0w_%x6Zzsn*M~22nOkLuYXc+KFHxVC=AppWM zHX}f7&?kve3R0j!Z&LcG9t*2kjQA`FtGuaDdQCM=UXE$1rXvtjvEHEH=~}b|QDNPD zg2D`P2M$;sjp}5*bavs#>bru)*3tzcBPfG7X9V%bCRabUR`I~qS+LBuoX3>uW%6Pf z=8>#lMlSBjQEy!_@5Hr~_t3=URX0Gy0E&_26{OMNDPfb>qtOT-q2I7>$P$jq4bv}C zJ!nJ<1ac!!*Q0vftwtW%IZS)1$gSU)tINbS;&5@%{eQ-YK`UL(F!AE#tKF?=yEKrs zBihS@ZF7jwpXM0CHuaNBhs?}}U9EILeVI!3WJccq;oyUtPqEh@IAP6ykT@;8+Hrj0 z?Vlfbcx?X^7=3p0VLmkd#-Gv8wmtGf?wN(rb2CR7|FG3|Y1}zl<%#WQ z@6^vax6Y5*o!uvIXJ+Kk^U~j+C$E7XP)}_nxhMQ-~E2a>p*Xt{)d^~4G4Dv4;-fH5FVcxXYy0MQp{Oz`83A@Ja%G4a8Kgpm58J{T~8s1Jy<_mx&#AdQ#o z?d;6&|NZ;_=g`n#|EAU_S}BU!lpIK;$(s(IjVI$L%L)YY)>u4n2KJNtb=Wip(_x7BF6Lt2I;ZX z5FN)ZqU8`^H2?%UqJ(%k9F9hxr3D}W4n#N}u!5ihS(QY(_G5@OS2xsjVo%K$`HC@l z-*;4wD;A5Pq8P$%j^hZO3>l>5C}r50ClZ_b0*zF#kj64b_d~ z6$VIZQ>liHReiCnh6&H_n<8P<60$1V%S=0nOCt|Yx*F=6B5rOEqH)x?i=dC)4910e zp@!-%(}EBJ^o~)})^XA6SSElH&_^+bRE=kW$nr`CNGh+WQiKH&6@V%}=I{tuKC zm?s66RzL}5bm+tTf^|(buxmjQuxY^@;v752(2X>zaa_PIF-*J@m+vRz@gWx*W`P`d z>Hc0i*%z02MV471#8=!+rPQSD`Owx-G7)1)azdu5s|rMdD2g(Rpa|Hk9*(e4B@$*4 zFF|M^2sB-(%O|il8Bm}uuM>Gm07@2$3aeygk!?=HwuX46* z?2KcE){K5X@;j0UCM^8|**~|U2SBu?;yeExq;JuqyzSQ@1lz+c$J+JG%Ylz)6K7rZ!TOs Sb@s0yL@L=gm^j}%{^mc4t&ljBz1!V& z?WT)!`~izG;`|i`1|pj9kA^s1LWU?Brjls!|os4x!GSXZ?K`H1eY6eCV#Kz4<1IT^QCQ~q@>H+HF^v4vbDgkPTH^#<{ zAk3Q2Z*eX^)fn>UdgixMW#pbf~PmeVb%7@$hL5*|C#3`Le8c3*&McMOur zSPvOQCL{$HOUo?FlRnYK2_BE%-$wE*&oZo!;aHmIC03BUZnAt)*qW)NrFf{LYzuz{ zsH|-p62lY<1y{lCLS}~HL{Vf|p5b{KBWP<-w?UECt>y|t2wJkK8n%jb(qRNCl(z#E zR=VJVW>jf)s~jdAFs29$hI6rwOC_KpS8+z(%$1BQG6Qo^gSu^DELX)ES!5$Ci6l29d8UNXhL8kGl5X9 zlBk}FWRmAyEZMR{)fH5*S{H4AAz;G*g|o)dtefV<1nZSJQS!KH)+e#-0yKscH9h!0 zC{8fv!{}?ExMUPygPVdCSxO^Q1K43z0~yE|dWIsaVU&U>hfHi3t8*`&j|PK1CQ7S0 zykNyUw~*0rP~b#Cpm`Tpk{gRjQQfkEF2iUjK;huHR8^5YP;q-bUXJz)P@z*k4^M+s zilqe}C{U2yZm7V@{t%M$P6<@@D|kOhi@Y}tA?@*skQRL&K>JfZmUeRjulUo7->dj4 z`?s1ZK7t^3+dR&}bZSS6s20xZVEO25hyCSIP9;m_A_3Vs1_4TT@(&fNJgqJffm`!n zfwqu^nDhp9u>iAB+AaVSwr6m(Zd!NDa^fv8aAWDWmb_)8Utg!UBfT6}IScd*#66Xv z9A_A(`z$1dxjtW|wMCt(0>|yo8CLr){-}0tsAFmq_hovYIY$sRHPKLeBKg^$ug}+y zvW+9dU!8gE#a)NblC6&%>za)3-u2}hO>;XZ&vvP6;2$q-{dQpb>fga_duNkZ9;&_n zr`q1b{k3mRJ-BnYv)|e~5!^%(V>K<(2VZZyukn?c`8^{?H+&dARAc>k&q&=zW1qKu zb7-*c!rhZZZ`-+F{%P(%PE3y|S6aveowbp_hTmy=e{`o>G$ad+p{6MZf^W!NAlmMsgAX$I-fbwapvTT_t!MSXzSXezwJ4`@5qt! zktz1lu@zc=_nYDM>O>BWp1Ab#11sm(|6JQU((vqf Y!~Q}vd?9@Lob!i^hPy-WZFze1KQT;ZSpWb4 literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/id.png b/icons/UI_Icons/inventory/id.png new file mode 100644 index 0000000000000000000000000000000000000000..4469591d36f52d65a994e06e2ce963ad6120374d GIT binary patch literal 1940 zcmb_dOKjXk7y1@p#u{?J>5K z-7FjshlHY>I8}%Pf(u*_aNq(`Ri#KAsT7Wgs$4)w<&hkE3*&v56q2-xuw>78=9~Zd z{qtX0n4cXPerlNGxRLT)X_1Y5_C7qszDc{i$A(kU+~tJh9yy)81Kj%`oZ+~k&;80$ zy5wAdm&VMJ+K*M*9*I+hy)dzW z$t@JgiUcy3IzSiaxM($Qbd3TpzNhY=QOgT_oTvrap>UM?0FV(R$~wvjB(p6AQcMMJR+LsTWD zW3J=Ca+stj#H3uZ1r{91_g$zKbY1g2B5JDXimIYx(KL}Fx~>OQk08S|%>MZj#j6|C@ZBy#v=SOJrw>-R0RdZzAj`Z$l(xaf(@! zd13U&JFG#yTP{simQLZj`}-5ylhuuv9@>J-I|E?mP4d&L?c3LX=B`e@_S@d+y&K?- zFChP^|CsZ~ttNK?#oIgAzPtLwqfh_x*49_n^41;Bokhtr(&p&y=EmTVKK#!0yJxRR zo6^Mi*~#5=++X{*w-@X)jh9<>bNTznza4z#$(uh6d_3~r&6!_68JziMklXn%*!}3s SjX$%#S)QIReRh8N?Y{ws0&39! literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/mask.png b/icons/UI_Icons/inventory/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..82e51089379638169f03a711e11b1a6312d06ba4 GIT binary patch literal 1930 zcmbVN4Qvxt9KSBv0$U&mP6!Kf-i)B(dLR8*ds&BDyN?FDEnCe-LbhG+UfavsyW8Du z{U8R=kp}t=)l@>ju$g zPd)0C6o6WdB+ii}jh5Jrl+|pu*|wlGNs|OwLQo`5(+p{4EG9I4VbGew`MJQv_KPBoWmaS)bIY zIWVYn!G*A#r4`k5m|(z&C@T|`k3}SWR=qJ95Dh?;Iu#C7c0rlNx;HZ8Q~;|< zidT|CnMBoRB%?HKB+<=Hg2+n|_4!2`fQ!|D1A|$kaMFZRb}wmRC_7`a;$#U!k_%9e z#0&n||DZ6zG{soVcR^vv@T|r@6wGssUsA#>bXW+p0YJ!N07J83WSmk+QlMd|&a`;m z?R3^Fl3xhH1+}5N0(Dn9t(4tr#c3mzlI!s>Zc)`(kppg*1B1aa3Ifki6lb9*s|6=1 zK;ytI-7Y~O_++0G3_NUgxWTv|R(Ac(=^ zc9naZ$1lEHSN9sZuIJA~wXg5nAD^YWC$7}L`16kha&0AA&!G3Gm7m16X9GE|djE$TrAB-9Pn(gh~k!;N$S(UUOkN?Y~#53 z&fN83LvncIhl$qrzyGZ}nTr@#4mgjBy?4Gnh9re;eQ)&LuHtuG>N%6>Zt<>?4-OW# z{Bi7quTPE^7OlOu^S$rpkM1e*@1Ja``lw+3r>WVMgA*mY{(XDqhW@v7SJt`)E81TD E4>iM=I{*Lx literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/neck.png b/icons/UI_Icons/inventory/neck.png new file mode 100644 index 0000000000000000000000000000000000000000..78ad3ce3b1c7d608523bea7f3b8ea62fcc789924 GIT binary patch literal 1852 zcmbVNeQ4Zd7|+I5+Rmv^=Imf2W*=^>N#5jgxhC87V|TaqWY-?;K`RvJd&zruv3Gf6 zlIvZobgfWX{}KfUIzeS_BFwF9D(YY#;&g7v91a|;%*qhPX4xO=A0jf}ThI`1J_O99*^6!j{{6TJYR_C)0MXhM?t|ipl z?>tFS%dZ=Wez!liOVx0WgE~$ku9&k3nxeM17A>d^B9~60v|$FB+b2#jw4n!?K7S0v ztPsi=og+5t9qCGFBZHcvGp#N3_M%D%a>#{rF_$$RwHRbdyee6HuX% z`(trBgl$C29AGs7L^_~wf-FhR%}uljM8JapF922)RUoT=A6@=2#G9?B)SmFpvM=%! zWHPR6sXSjO6u5$q!*-e%6h+~I$crLN5Uew7y0FNa&iV>N7&)44SgwIh+GB)CoOgo^ zQ92hw&Z^RyPB~2^VSEuT8SG*wgBPK$ zZvV#tNo_1v^|7FqT(0WEaU(+{jB-L2L_3LL3-LY3!FgLlks%^;y%&w8hHL~~Y$q_z zRtgobNTx-R1N7!T!_;xX*)q=ng`tar45^yH0v{_V3E)>npprERr~u4CV^}v*!~cVl z0t*3EB9`-@q-AvI!ux`CO-*4t2T8z24yF-rnQ4Zurcn*yEVhYbqRuz}eKZt`+c;%p z$%fO@v4f6ALb9O9GAnXINp38rMoq_sriP;7AVZSF8HTRvq9mmJffOssP-caI1X!pi zlWf2bL|sb;BtL)^`!Lq>UJF$0bz%=SO;>z^!pc%oVxb~6v(1u3=oB3yKk`FCNLB2+ zZG#*^n0;s-?_hepqjnk&sp@d~==7o&%d4zGm)eEM@{U1}(Y*R2ohiRI7RbPZMKDKO z$RI*`kG`0LIXLAOppDwoBwP3GJAN_o4jj6-^am^6BGT{f(}$5>POFxIW*U*1$}?Ut zyf=O3io)MruhQPUNmYT9@#ih8bC*0+CpR()wN2*o#e=_pM`rq+Xt+JmKQX&6`si2S zk;qHEH+oameq8wa zui%ogzN;7Byxu@geB{`QE2sC2Czo=?t7~i9_q1*LqV0t8#ybD88{MD$b}@C~7w6}v z=-u0Drj~X5G;n^y*73$a51c$Qk-j!N>6}V^7Tq70*KPQKn%R5uj&HfXHgqk&d*^um z`{VWJF1;|duekcTqo*4ZY?E!A9XTdMh@B0>wbcK)Xc>a}t0rPNTB>(^b literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/pocket.png b/icons/UI_Icons/inventory/pocket.png new file mode 100644 index 0000000000000000000000000000000000000000..f42399dca0f57aee93ea1c8890690e1cb09eb643 GIT binary patch literal 1831 zcmbVNe`wrP98YWKX-9`+!ba!DBdxH3xxA!F?h<-#-Q9I};&ol`pyxzvE_rtsddW+Z zw0A8-y5e9(|58zKLzrX!rJaJ(O$Ceo;U9MYh-2;kbIL$jw<=bOO!Vb?chuQ#V}T?u z?|t6q`}zKO-%R)S9d2uFZ>1=zEt!Z7kT>w}#~vk5cVglX@_O7(yx>yQ)*b%cM7?`v z7e#IP$;f2AZ0dPM!)8#`aSjD5rcKZk)qS91tJ*N~Kn@iQD?;D?;wu^$dW0SnQ!r&m zQPD_DI4C{Qm(eDMHCd+*JPW!j3L!9&r-F)EvRtJSp{u+K8T->L4XO}tI70XM20=E} z52DyXKng-egOCRyImk(Z5Dq^Bc*sK*hFA_VJg-1W5xYR`qKP#}&npA5-kL4)6`_ls zXDcjQE|-Jlt{`>_EGNq{3wf638G>Nkam!OHjOBLJ8DhxQ9K-euYyqE9&EZimLKCHH zE|_+M)^clMA^~G7s?Bmi=(|(}>RJP5k2=yAR)Q#o; z7$Bidr5ZLi)MA4Y$fU-)x%B(<5E3Q z{dLKJ=YtUJ8#F8(m)-sAHlUd5p$JW~#xbyq;p7Yy6;4(JnSmh%!Zm0L>qdV3e^8QO zF07DDuZNP7(N#~qFId-B}RpE?y@+{nuoyCzCz7^mfm-DfmSE z(gzo#7bmZ`!?T}XiS5QepPx_c4)h*-?tJIc`>)NOUAnVVi6$uU+w|4tePGMjl|3!v zm!JOS#7gPUk91?C`^a!^+l{|=&IRJ%y*zc(OE2tY!T!Mazu$S}rEmVurUN&6Ps{}N z0%+bc-Wxg@XXaPf$vrQAe5-xe?2eHiPoFz}>dURKpFUbX^@eyg6rcU}hvP>NZeJa4 ze`@Z`a?4vr)}<4d7yoIx-MrBGX4@s|SXb+O>(Dzh^7c>jtIgM@?VGQpdl#B#2Dz1M q=O#a!%XVH^Js6q#aIq!OvE1}zYpnU}3!ixYuPhnwi_IJwn)(+_K2}r! literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/shoes.png b/icons/UI_Icons/inventory/shoes.png new file mode 100644 index 0000000000000000000000000000000000000000..d20f7ef4d10615f467b90ca65fbaaf85b49fd4ec GIT binary patch literal 1817 zcmbVNZEVzJ96yjPz}#47G`=LHD-lp^`?Po0yEfd|xI4E?=(vn?oQ^Ph?bExuYoF3~ zyW44^A!;NVrzVa?x5Pjq#18}w(I8QL`Q``02SsDXhdGplM3g9M2);bMmuBJygr>bd zeV*U*`~Utg&;RzP`+Hkg-@iH#2(+g95(D^6c+bja{OeRIzv0&^tM73q5NNr}dzJ;> zoW3^@XqwkESvMP`Ut@R4lp3Xuz=z?5%6L-NYpP9TeDS797sG`w}roA zWWjYUkzq=u5?u=Vbf^x=9S1wbgv#!CAfR1A8ma8L^@EGMB8g*kN zR$6nxuzXt6sfP&%j48_&!_vTWsR~pTA7_o)#j0^tVPFv&&~zP)WqqtwKrV6$XbEb6 z`9B75XeG(Fv8Wcq@J%>watyms56GfuCo^tAW&k>9)K*|}49i^Sd1Hxj8_F)SGYAzM ziApyl6C6haV&hKTR8h%!Xu$@UkX;xfan@K01SwX?fUw94VmL&BhzLLpDj`+Z#{UP! z31*`r&eB3CE*VvJ<(q<4MbwaO$k<`skn@nS%sff>VHD%2h-_>as|zliPsQVD8)~QBM73N*S@DiRj8we*LzS#g>x)F-);y@u zmI@G)-k>gOFb8RFNw#569!Kk@b;m3v-jT;{EdAD!w}kZT>-2V{*Tbq5WHS$OPi08Y z8OG~AwWKiD=c~H5pi?#AxczxUzwhD?zjH$qQ`@*N@9vmCk9+!TD$$e4e(}>AQp>*X z_80zo?Vg!y8$U`k&3t+M*ukO6gC7S z*>i>Cr#8Jim%Zcb3$qj9(>?1ie6wQqT4&*E$J%e#|B^cL!KMfIUuv3Wnh#t)^z_i7 z+>1y5ZtiLueSX`Dsn5Re-aYkj`mIlfcbywdc8xTh{#0{rZW!=brHXyi&>j#L=yL_WcWGY*4lU literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/suit.png b/icons/UI_Icons/inventory/suit.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c48e8069f75f8d3c100e570f4e18b4bd543697 GIT binary patch literal 2073 zcmb_dZEO@p7~a+^LTQ6WB?Q0LZ37zF-Pygn-R|0xws(cQ*sHZYp#1P-Z+7l()4Sbu zciX%6hqi=h{9!_DqKN_09|W`l^20PGn4s1wIbw|(v5CQ8V`2!zl8RztqR!q|3ba5P zFWKAKnP=XQ=Y8g#H@Z5v*Vk^WB?zKE5)O6aH|ajBSK)tqXy___tue#9ZGu>PpZio1 z@9y6~5UWmTv0kS)+95+D!zii|M@%kbVl+WCZ_Sws>_ZM2M@dZ&Q1howQ>3N_s9pXj zh?+r^(!xU)>KW>c!J$4Vsnph|$>yAl4P=m`khx4+x8+=bD%h3r*qvr6vH)@V0#vK( zknD|ikwL>kq`&|g0>G1^#BhSo*VMFyDsiLV z2~arFVhS0vVpg|HG-1NnoMN&Z1KgAfKowSSX1|p#I9DNy(kO#;$HrK$f;CfyW7sKU z8Pk>J>l|QeqtS|wC1c5CDlTlNeE_FXB4kOl9UC+e+l_3a--4)p0EgM&W@E}h3n`9a z#S9}|E>u@JWRm9@Kt8@p(^VsDKe1>73Mmc>P`GLw4ZJib#eiStB$*dzAj$v~p;1HC z5`#BEae*-uh}S`}WK_jbZVOhSoG`46f)my3$Gj_9_d=nzFh0Sc4DXqqZZ67Y#gKs4_Y{4_85 z5_FR!A{uZ|5#n9}055h_sF)lwV81JY@_rTXhtMlZekjs$2>i6y?-Oas$E&m$S9o4k zMb!tz^8RNn4Ie=zy<$7=!F07Fhcz2lb+B}FdeF*%O|y=D~Y?#93iI14j#)N9@w|z?ba{+FE-|K-n!BA&BCK^jUB%<3=cds z{MWgeO~RF(WZrW*|Kow#Lz`RFhkH)FlAdH+cVFnKZk+jg_>AYh^Wfm$#J`>sSEm-P z)y&N;H0D1z{QNV&cg#yw`6nM(J=#D{c|M<*tp2utwr&1oQ~uKF7V&;(r+wt5BH6NXvTtD{ljA#AX?;rflv+%>YacXSGh%o=^npFMB zIC1T%iDScm3|>f9kFNVP{pZLxp4hL$%*^JWQ`JB1zdQWdw!L$2ewY|tJ6reqQAm&c j^UGzXp>8}IHaw3U@oi|xPxlVEmv^MSGjzP|r9J-vG{UPI literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/suit_storage.png b/icons/UI_Icons/inventory/suit_storage.png new file mode 100644 index 0000000000000000000000000000000000000000..9722eb10297ab295a9c9248c58ac4426acad96c4 GIT binary patch literal 1976 zcmbVN4Q$j@9PcKF$Ry73kp)F+(W#mJysus19OG_mm#kxsBbH=jI17MkNcJf#{sd3||_I0#Os4q5>u=MkkJlC<(saZWlnlLX))oLYo<+|Jr%>SX@OdQBYNZ3J!LX_8tL>Y^twr_eaOJDqUqLMfW6RPiea|n zaxi3uggRbS;}nq8Fr-p(4WTh?L1jtMwrY@Wgoc{XMOfW}WX`j^(RiN@fvM_2RgFy~ zs$oJh&T$lj-@9B^BsFQ=cisl*2PX6qC~GXq*h$tCWSl(f;T>+0aq|o_4i!{Mj;c>g?yw2YkK;U5&^w zv|u#WEW!iTJ{Rk8xk!#;vvLK24=9ES6cGmeUIGP&l4XgH0J}3%;S7^Z#8W|v5^^zY z=iFpOWbK?Nh8YJ3CieSPF=3U!#C{3wce+^UVx3Ns16&0uIUJDm0Kk&r3bzCq07N^J zx4%S}(Gdi(>*lczrd2z9t!$vIw&sq`Qn)5JiphAkTzDW_$G}U7R{o(x3+{c*DQlI@`YbY?rMh=y+yiYsbo+cby-b9i86TiX^P6Il1y<2&s;qAdsC zewYrFjP|Fx+E+{&EUmORBnBQ_7%kzn>1#);zFBv8*8a5{<3}bBbYt(99)5Axqn*cg z-M(pd+2N7jmlY1}c&Ga_$FUvLUmG?_x;>J4sc^+{ScMJte_-D{?ZB4iGodfA*ZU6r zG)E6Es>8RRrT#cGEzS5l^1uupDt=|O^-2)B}8=| sI?4@>B@1q@R%>UL?QyPJ+=b1o^uDokjEDRFvi_}s>U#gSMXT5U2WnlQ>i_@% literal 0 HcmV?d00001 diff --git a/icons/UI_Icons/inventory/uniform.png b/icons/UI_Icons/inventory/uniform.png new file mode 100644 index 0000000000000000000000000000000000000000..292b3324b5bd9b0a82a56fb0de737bfcd3e58bcc GIT binary patch literal 1885 zcmbVNZEO=|9KQ`&wl%{s38KU0xP8F4dtKk|ps=moOVN%}8dnIIUGJW@=X&=Xcek}` zGO~@BI46z|OF*5*7-K?W*glZ>!4EFZ511trG$9(IVu&a~nK2E~7}4jtwi&oNLX+z~ zFTdya|NUQ{|BZ)xy6S2luf;H|F4!H2pf_$m)m7-<%;oavRik$wGBM2Yi2YPxZ%%K+ zu#MN0Xv~U*o);vjQlbpwfXb>mLSxwOy;)t9`hkVVK~m9t#L~x~5V#`yh+bES3F&^2 zQo3^nIGF2+O1XZCmx;Z5@ZDJf5vaft@vNHGOd;zd3cLau+sib87a&%@k7%>?rgEUZqW|;`fma%#YTF^|vHK@zm zcNsvT4TZ`!R@I`aWfP{=F^JqK24q#V86DCA9RVgBFeK11h-5a~-spng0HOtrD1_-! zqQWJ~ILA>8{$#JB$uMI+wc-E>h!*e>C~GXqw2&+xWn2Qw3-;6_Fw8PE1Z5>L^dBfn zFvke2^DZbV8CkT%b-}VEB%q;+$YDhllYrK>B!QR1DEMI-8ptqG*Rpaw==X;Wm{8Ja z!;Eyb5(ox-1PTtND6-&XT#_fwu%yT71;|-voaDW3 zne<8`%fwk%+{2X52cR@ymq6*fTsYs-B1s93Bi+~e{}JXf;6 z-%!vM6w?pPV_!_Wc7$%lL|GjwUY&#BSaFnA@Itu=qGVqKA0gTK2QpDyR#uI`{dllU zn@Isg`agBC3^QTE%7_MNOQLA4TX*zY;!SaIJ?Zz8yfviXTc-~qy%<(0C2C24dMZuW z&d_%ESxyRlZ@vn9D>_vPj@q9+EcabBDR*w5A!-Bl<)arr-;ZGxcrefwjm`ghI<#qw zsqf?`lfPjvwn<}WPR(9U5$Vscac#?ypFiC&bhhfb8}CLBFHD9Tw!%?sPg8hTbmCg< zX2Y$T;Y?@E2bqP~*N;}tR4wj2^WEDR|M%vRkso*DjAPAHeZTlCw@DI0L z9OOR-92eLl(-Q}JM~3t7UA&Zyyz*N0Ur%po(A%fxdYT^F{Dvc0nHOqn^*hH;IxZ}Z zUfyUm&U~|Ne!M#5Xr-<+R#j-bsKZ|@e7GU4-dGx4d?@hk?7oLn=b!kf`Rdit#mS}7 z+I;uOrfK;4)K_n*P0!3tHqPI9=fw3{U+Zv1HGK8UnZEk^mwz~PGIDMzAB)ak*>QH~ w+^stC_Hpj$pM2ux$f?FI`2Owd`R^;~OMm=yKJ~)(Y5M~h?C1%cYwsKT7f3W~>i_@% literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 6b58f711bf..79aef22354 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -117,6 +117,7 @@ #include "code\__DEFINES\stat.dm" #include "code\__DEFINES\stat_tracking.dm" #include "code\__DEFINES\status_effects.dm" +#include "code\__DEFINES\strippable.dm" #include "code\__DEFINES\subsystems.dm" #include "code\__DEFINES\tgs.config.dm" #include "code\__DEFINES\tgs.dm" @@ -658,6 +659,7 @@ #include "code\datums\elements\snail_crawl.dm" #include "code\datums\elements\spellcasting.dm" #include "code\datums\elements\squish.dm" +#include "code\datums\elements\strippable.dm" #include "code\datums\elements\swimming.dm" #include "code\datums\elements\sword_point.dm" #include "code\datums\elements\tactical.dm" @@ -2612,6 +2614,7 @@ #include "code\modules\mob\living\carbon\carbon_defines.dm" #include "code\modules\mob\living\carbon\carbon_movement.dm" #include "code\modules\mob\living\carbon\carbon_sprint.dm" +#include "code\modules\mob\living\carbon\carbon_stripping.dm" #include "code\modules\mob\living\carbon\damage_procs.dm" #include "code\modules\mob\living\carbon\death.dm" #include "code\modules\mob\living\carbon\emote.dm" @@ -2667,6 +2670,7 @@ #include "code\modules\mob\living\carbon\human\human_helpers.dm" #include "code\modules\mob\living\carbon\human\human_mobility.dm" #include "code\modules\mob\living\carbon\human\human_movement.dm" +#include "code\modules\mob\living\carbon\human\human_stripping.dm" #include "code\modules\mob\living\carbon\human\inventory.dm" #include "code\modules\mob\living\carbon\human\life.dm" #include "code\modules\mob\living\carbon\human\login.dm" diff --git a/tgui/packages/tgfont/icons/air-tank-slash.svg b/tgui/packages/tgfont/icons/air-tank-slash.svg index c72fca54fa..0a9c4c0df4 100644 --- a/tgui/packages/tgfont/icons/air-tank-slash.svg +++ b/tgui/packages/tgfont/icons/air-tank-slash.svg @@ -23,7 +23,7 @@ c-13.66-16.01-2.33-26.15,22.14-49.83C228.23,47.45,234.41,19.82,248.07,35.83z"/> - + diff --git a/tgui/packages/tgui/interfaces/StripMenu.tsx b/tgui/packages/tgui/interfaces/StripMenu.tsx index 3832ca087c..d8c466f58b 100644 --- a/tgui/packages/tgui/interfaces/StripMenu.tsx +++ b/tgui/packages/tgui/interfaces/StripMenu.tsx @@ -8,6 +8,9 @@ import { Window } from "../layouts"; const ROWS = 5; const COLUMNS = 6; +const ROWS_LONG = 4; +const COLUMNS_LONG = 11; + const BUTTON_DIMENSIONS = "50px"; type GridSpotKey = string; @@ -71,6 +74,14 @@ const ALTERNATE_ACTIONS: Record = { icon: "tshirt", text: "Adjust jumpsuit", }, + enable_helmet: { + icon: "toggle-off", + text: "Extend helmet", + }, + disable_helmet: { + icon: "toggle-on", + text: "Retract helmet", + }, }; const SLOTS: Record< @@ -209,6 +220,142 @@ const SLOTS: Record< }, }; +const SLOTS_LONG: Record< + string, + { + displayName: string; + gridSpot: GridSpotKey; + image?: string; + additionalComponent?: JSX.Element; + } +> = { + eyes: { + displayName: "eyewear", + gridSpot: getGridSpotKey([0, 0]), + image: "inventory-glasses.png", + }, + + head: { + displayName: "headwear", + gridSpot: getGridSpotKey([0, 1]), + image: "inventory-head.png", + }, + + neck: { + displayName: "neckwear", + gridSpot: getGridSpotKey([1, 0]), + image: "inventory-neck.png", + }, + + mask: { + displayName: "mask", + gridSpot: getGridSpotKey([1, 1]), + image: "inventory-mask.png", + }, + + corgi_collar: { + displayName: "collar", + gridSpot: getGridSpotKey([1, 1]), + image: "inventory-collar.png", + }, + + ears: { + displayName: "earwear", + gridSpot: getGridSpotKey([1, 2]), + image: "inventory-ears.png", + }, + + parrot_headset: { + displayName: "headset", + gridSpot: getGridSpotKey([1, 2]), + image: "inventory-ears.png", + }, + + jumpsuit: { + displayName: "uniform", + gridSpot: getGridSpotKey([2, 0]), + image: "inventory-uniform.png", + }, + + suit: { + displayName: "suit", + gridSpot: getGridSpotKey([2, 1]), + image: "inventory-suit.png", + }, + + gloves: { + displayName: "gloves", + gridSpot: getGridSpotKey([2, 2]), + image: "inventory-gloves.png", + }, + + handcuffs: { + displayName: "handcuffs", + gridSpot: getGridSpotKey([2, 8]), + }, + + shoes: { + displayName: "shoes", + gridSpot: getGridSpotKey([3, 1]), + image: "inventory-shoes.png", + }, + + legcuffs: { + displayName: "legcuffs", + gridSpot: getGridSpotKey([3, 2]), + }, + + suit_storage: { + displayName: "suit storage item", + gridSpot: getGridSpotKey([3, 3]), + image: "inventory-suit_storage.png", + }, + + id: { + displayName: "ID", + gridSpot: getGridSpotKey([3, 4]), + image: "inventory-id.png", + }, + + belt: { + displayName: "belt", + gridSpot: getGridSpotKey([3, 5]), + image: "inventory-belt.png", + }, + + back: { + displayName: "backpack", + gridSpot: getGridSpotKey([3, 6]), + image: "inventory-back.png", + }, + + right_hand: { + displayName: "right hand", + gridSpot: getGridSpotKey([3, 7]), + image: "inventory-hand_r.png", + additionalComponent: R, + }, + + left_hand: { + displayName: "left hand", + gridSpot: getGridSpotKey([3, 8]), + image: "inventory-hand_l.png", + additionalComponent: L, + }, + + left_pocket: { + displayName: "left pocket", + gridSpot: getGridSpotKey([3, 9]), + image: "inventory-pocket.png", + }, + + right_pocket: { + displayName: "right pocket", + gridSpot: getGridSpotKey([3, 10]), + image: "inventory-pocket.png", + }, +}; + enum ObscuringLevel { Completely = 1, Hidden = 2, @@ -246,24 +393,35 @@ type StripMenuItem = type StripMenuData = { items: Record; name: string; + long_strip_menu: boolean; }; export const StripMenu = (props, context) => { const { act, data } = useBackend(context); const gridSpots = new Map(); - for (const key of Object.keys(data.items)) { - gridSpots.set(SLOTS[key].gridSpot, key); + if (data.long_strip_menu) { + for (const key of Object.keys(data.items)) { + gridSpots.set(SLOTS_LONG[key].gridSpot, key); + } + } else { + for (const key of Object.keys(data.items)) { + gridSpots.set(SLOTS[key].gridSpot, key); + } } return ( - + - {range(0, ROWS).map(row => ( + {range(0, data.long_strip_menu ? ROWS_LONG : ROWS).map(row => ( - {range(0, COLUMNS).map(column => { + {range(0, data.long_strip_menu ? COLUMNS_LONG + : COLUMNS).map(column => { const key = getGridSpotKey([row, column]); const keyAtSpot = gridSpots.get(key); @@ -290,7 +448,9 @@ export const StripMenu = (props, context) => { if (item === null) { tooltip = slot.displayName; } else if ("name" in item) { - alternateAction = ALTERNATE_ACTIONS[item.alternate]; + if (item.alternate) { + alternateAction = ALTERNATE_ACTIONS[item.alternate]; + } content = ( { {slot.image && ( )} diff --git a/tgui/packages/tgui/styles/atomic/centered-image.scss b/tgui/packages/tgui/styles/atomic/centered-image.scss new file mode 100644 index 0000000000..cce5bfdf2c --- /dev/null +++ b/tgui/packages/tgui/styles/atomic/centered-image.scss @@ -0,0 +1,7 @@ +.centered-image { + position: absolute; + height: 100%; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%) scale(0.8); +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index f38d47ba2a..c30d87bf38 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -11,6 +11,7 @@ // Atomic classes @include meta.load-css('./atomic/candystripe.scss'); +@include meta.load-css('./atomic/centered-image.scss'); @include meta.load-css('./atomic/color.scss'); @include meta.load-css('./atomic/debug-layout.scss'); @include meta.load-css('./atomic/outline.scss');