From 8171f220d91f7c3e28de37618418efef59b9b336 Mon Sep 17 00:00:00 2001 From: private-tristan Date: Mon, 19 Aug 2024 16:54:16 -0400 Subject: [PATCH 1/2] Ports TG stripping menu from upstream --- code/__DEFINES/strippable.dm | 30 + code/_macros.dm | 5 + code/_onclick/click_hold.dm | 5 +- code/_onclick/drag_drop.dm | 1 + code/datums/elements/strippable.dm | 536 ++++++++++++++++++ code/game/objects/items/misc.dm | 4 +- code/game/objects/items/weapons/twohanded.dm | 2 +- code/modules/asset_cache/asset_list.dm | 21 + code/modules/mob/living/carbon/carbon.dm | 17 - code/modules/mob/living/carbon/human/human.dm | 118 +--- .../living/carbon/human/human_stripping.dm | 272 +++++++++ .../mob/living/carbon/human/inventory.dm | 62 -- .../mob/living/carbon/xenomorph/XenoProcs.dm | 4 - .../mob/living/simple_animal/parrot.dm | 13 - code/modules/mob/mob.dm | 34 -- code/modules/projectiles/gun_attachables.dm | 4 +- colonialmarines.dme | 3 + 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 tgui/packages/tgui/interfaces/StripMenu.tsx | 382 +++++++++++++ .../tgui/styles/interfaces/StripMenu.scss | 65 +++ tgui/packages/tgui/styles/main.scss | 1 + 37 files changed, 1324 insertions(+), 255 deletions(-) create mode 100644 code/__DEFINES/strippable.dm create mode 100644 code/datums/elements/strippable.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/interfaces/StripMenu.tsx create mode 100644 tgui/packages/tgui/styles/interfaces/StripMenu.scss diff --git a/code/__DEFINES/strippable.dm b/code/__DEFINES/strippable.dm new file mode 100644 index 0000000000..f62c4d6c1b --- /dev/null +++ b/code/__DEFINES/strippable.dm @@ -0,0 +1,30 @@ +// All of these must be matched in StripMenu.js. +#define STRIPPABLE_ITEM_HEAD "head" +#define STRIPPABLE_ITEM_BACK "back" +#define STRIPPABLE_ITEM_MASK "wear_mask" +#define STRIPPABLE_ITEM_EYES "glasses" +#define STRIPPABLE_ITEM_L_EAR "wear_l_ear" +#define STRIPPABLE_ITEM_R_EAR "wear_r_ear" +#define STRIPPABLE_ITEM_JUMPSUIT "w_uniform" +#define STRIPPABLE_ITEM_SUIT "wear_suit" +#define STRIPPABLE_ITEM_GLOVES "gloves" +#define STRIPPABLE_ITEM_FEET "shoes" +#define STRIPPABLE_ITEM_SUIT_STORAGE "j_store" +#define STRIPPABLE_ITEM_ID "id" +#define STRIPPABLE_ITEM_BELT "belt" +#define STRIPPABLE_ITEM_LPOCKET "l_store" +#define STRIPPABLE_ITEM_RPOCKET "r_store" +#define STRIPPABLE_ITEM_LHAND "l_hand" +#define STRIPPABLE_ITEM_RHAND "r_hand" +#define STRIPPABLE_ITEM_HANDCUFFS "handcuffs" +#define STRIPPABLE_ITEM_LEGCUFFS "legcuffs" + + +/// 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/_macros.dm b/code/_macros.dm index 6c1f37b4bc..0e85827bc0 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -67,6 +67,11 @@ #define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } ///Accesses an associative list, returns null if nothing is found #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; // Insert an object A into a sorted list using cmp_proc (/code/_helpers/cmp.dm) for comparison. #define ADD_SORTED(list, A, cmp_proc) if(!list.len) {list.Add(A)} else {list.Insert(FindElementIndex(A, list, cmp_proc), A)} diff --git a/code/_onclick/click_hold.dm b/code/_onclick/click_hold.dm index 996f7ed2bf..e972821615 100644 --- a/code/_onclick/click_hold.dm +++ b/code/_onclick/click_hold.dm @@ -97,9 +97,8 @@ /client/MouseDrop(datum/over_object, datum/src_location, over_location, src_control, over_control, params) . = ..() + if(over_object) + SEND_SIGNAL(over_object, COMSIG_ATOM_DROP_ON, src_location, src) if(src_location) SEND_SIGNAL(src_location, COMSIG_ATOM_DROPPED_ON, over_object, src) - - if(over_object) - SEND_SIGNAL(over_object, COMSIG_ATOM_DROP_ON, src_location, src) diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index fff5e9200d..4dcc0d6468 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -7,6 +7,7 @@ */ /atom/MouseDrop(atom/over) if(!usr || !over) return + if(!Adjacent(usr) || !over.Adjacent(usr)) return // should stop you from dragging through windows spawn(0) diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm new file mode 100644 index 0000000000..e0daaee74a --- /dev/null +++ b/code/datums/elements/strippable.dm @@ -0,0 +1,536 @@ +/// 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_ATOM_DROP_ON, PROC_REF(mouse_drop_onto)) + + src.items = items + src.should_strip_proc_path = should_strip_proc_path + +/datum/element/strippable/Detach(datum/source, force) + . = ..() + + UnregisterSignal(source, COMSIG_ATOM_DROP_ON) + + if (!isnull(strip_menus)) + QDEL_NULL(strip_menus[source]) + +/datum/element/strippable/proc/mouse_drop_onto(datum/source, atom/over, mob/user) + SIGNAL_HANDLER + if (user == source) + return + + if (over == source) + return + + var/mob/overmob = over + if (!ishuman(overmob)) + return + + if (!overmob.Adjacent(source)) + return + + if (!overmob.client) + return + + if (overmob.client != user) + return + + if (!isnull(should_strip_proc_path) && !call(source, should_strip_proc_path)(overmob)) + 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, PROC_REF(tgui_interact), overmob) + +/// 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 ((equipping.flags_item & ITEM_ABSTRACT)) + return FALSE + if ((equipping.flags_item & NODROP)) + to_chat(user, SPAN_WARNING("You can't put [equipping] on [source], it's stuck to your hand!")) + return FALSE + if (ishuman(source)) + var/mob/living/carbon/human/sourcehuman = source + if(HAS_TRAIT(sourcehuman, TRAIT_UNSTRIPPABLE) && !sourcehuman.is_mob_incapacitated()) + to_chat(src, SPAN_DANGER("[sourcehuman] is too strong to force [equipping] onto them!")) + return + 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) + source.visible_message( + SPAN_NOTICE("[user] tries to put [equipping] on [source]."), + SPAN_NOTICE("[user] tries to put [equipping] on you.") + ) + + if (ismob(source)) + var/mob/sourcemob = source + sourcemob.attack_log += text("\[[time_stamp()]\] [key_name(sourcemob)] is having [equipping] put on them by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] [key_name(user)] is putting [equipping] on [key_name(sourcemob)]") + + 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) + +/// 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 (user.action_busy && !skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED)) + to_chat(user, SPAN_WARNING("You can't do this right now.")) + return FALSE + + if ((item.flags_inventory & CANTSTRIP) || ((item.flags_item & NODROP) && !(item.flags_item & FORCEDROP_CONDITIONAL)) || (item.flags_item & ITEM_ABSTRACT)) + return FALSE + + if (ishuman(source)) + var/mob/living/carbon/human/sourcehuman = source + if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcehuman.stat == DEAD || sourcehuman.health < HEALTH_THRESHOLD_CRIT) && !sourcehuman.get_target_lock(user.faction_group)) + to_chat(user, SPAN_WARNING("You can't strip items of a crit or dead member of another faction!")) + return FALSE + + if(HAS_TRAIT(sourcehuman, TRAIT_UNSTRIPPABLE) && !sourcehuman.is_mob_incapacitated()) + to_chat(src, SPAN_DANGER("[sourcehuman] has an unbreakable grip on their equipment!")) + return + + 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]."), + SPAN_DANGER("[user] tries to remove your [item].") + ) + + if (ismob(source)) + var/mob/sourcemob = source + sourcemob.attack_log += text("\[[time_stamp()]\] [key_name(sourcemob)] is being stripped of [item] by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] [key_name(user)] is stripping [key_name(sourcemob)] of [item]") + + item.add_fingerprint(user) + + 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) + 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) + +/// 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/proc/has_no_item_alt_action() + return FALSE + +/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(key) + +/datum/strippable_item/mob_item_slot/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return + + if (!ismob(source)) + return FALSE + if (user.action_busy) + to_chat(user, SPAN_WARNING("You can't do this right now.")) + return FALSE + if (!equipping.mob_can_equip( + source, + key + )) + to_chat(user, SPAN_WARNING("\The [equipping] doesn't fit in that place!")) + return FALSE + if(equipping.flags_item & WIELDED) + equipping.unwield(user) + return TRUE + +/datum/strippable_item/mob_item_slot/start_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return + + if (!ismob(source)) + return FALSE + + var/time_to_strip = HUMAN_STRIP_DELAY + var/mob/sourcemob = source + + if (ishuman(sourcemob) && ishuman(user)) + var/mob/living/carbon/human/sourcehuman = sourcemob + var/mob/living/carbon/human/userhuman = user + time_to_strip = userhuman.get_strip_delay(userhuman, sourcehuman) + + if (!do_after(user, time_to_strip, INTERRUPT_ALL, BUSY_ICON_FRIENDLY, source, INTERRUPT_MOVED, BUSY_ICON_FRIENDLY)) + return FALSE + + if (!equipping.mob_can_equip( + sourcemob, + key + )) + return FALSE + + if (!user.temp_drop_inv_item(equipping)) + return FALSE + + return TRUE + +/datum/strippable_item/mob_item_slot/finish_equip(atom/source, obj/item/equipping, mob/user) + if (!ismob(source)) + return FALSE + + var/mob/sourcemob = source + sourcemob.equip_to_slot_if_possible(equipping, key) + +/datum/strippable_item/mob_item_slot/get_obscuring(atom/source) + 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) + +/// A utility function for `/datum/strippable_item`s to start unequipping an item from a mob. +/datum/strippable_item/mob_item_slot/proc/start_unequip_mob(obj/item/item, mob/living/carbon/human/source, mob/living/carbon/human/user) + var/time_to_strip = HUMAN_STRIP_DELAY + + if (istype(source) && istype(user)) + time_to_strip = user.get_strip_delay(user, source) + + if (!do_after(user, time_to_strip, INTERRUPT_ALL, BUSY_ICON_HOSTILE, source, INTERRUPT_MOVED, BUSY_ICON_HOSTILE)) + return FALSE + + return TRUE + +/// A utility function for `/datum/strippable_item`s to finish unequipping an item from a mob. +/datum/strippable_item/mob_item_slot/proc/finish_unequip_mob(obj/item/item, mob/source, mob/user) + if (!source.drop_inv_item_on_ground(item, force = (item.flags_item & FORCEDROP_CONDITIONAL))) //force if we can drop the item in this case + return FALSE + + if (ismob(source)) + var/mob/sourcemob = source + sourcemob.attack_log += text("\[[time_stamp()]\] [key_name(sourcemob)] has been stripped of [item] by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] [key_name(user)] has been stripped of [key_name(sourcemob)] of [item]") + + // Updates speed in case stripped speed affecting item + source.recalculate_move_delay = TRUE + +/// 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/tgui_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)) + if (item_data.has_no_item_alt_action()) + LAZYINITLIST(result) + result["no_item_action"] = item_data.get_alternate_action(owner, user) + items[strippable_key] = result + continue + + LAZYINITLIST(result) + + result["icon"] = icon2base64(icon(item.icon, item.icon_state, frame = 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]" + + return data + +/datum/strip_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if (.) + return + + . = TRUE + + var/mob/user = ui.user + + switch (action) + if ("equip") + 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 + + var/obj/item/held_item = user.get_held_item() + if (isnull(held_item)) + return + + if (!strippable_item.try_equip(owner, held_item, user)) + return + + 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) + if ("strip") + 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 (!strippable_item.try_unequip(owner, user)) + return + + 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) && !strippable_item.has_no_item_alt_action()) + 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_status(mob/user, datum/ui_state/state) + . = ..() + + if (isliving(user)) + var/mob/living/living_user = user + + if ( + . == UI_UPDATE \ + && user.stat == CONSCIOUS \ + && living_user.body_position == LYING_DOWN \ + && user.Adjacent(owner) + ) + return UI_INTERACTIVE + +/// 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/misc.dm b/code/game/objects/items/misc.dm index 1bdcab2ac6..6057f35818 100644 --- a/code/game/objects/items/misc.dm +++ b/code/game/objects/items/misc.dm @@ -56,13 +56,13 @@ ..() if(!gripped) user.visible_message(SPAN_NOTICE("[user] grips [src] tightly."), SPAN_NOTICE("You grip [src] tightly.")) - flags_item |= NODROP + flags_item |= NODROP|FORCEDROP_CONDITIONAL ADD_TRAIT(user, TRAIT_HOLDS_CANE, TRAIT_SOURCE_ITEM) user.AddComponent(/datum/component/footstep, 6, 35, 4, 1, "cane_step") gripped = TRUE else user.visible_message(SPAN_NOTICE("[user] loosens \his grip on [src]."), SPAN_NOTICE("You loosen your grip on [src].")) - flags_item &= ~NODROP + flags_item &= ~(NODROP|FORCEDROP_CONDITIONAL) REMOVE_TRAIT(user, TRAIT_HOLDS_CANE, TRAIT_SOURCE_ITEM) // Ideally, this would be something like a component added onto every mob that prioritizes certain sounds, such as stomping over canes. var/component = user.GetComponent(/datum/component/footstep) diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm index be7571fa84..36e0ea702a 100644 --- a/code/game/objects/items/weapons/twohanded.dm +++ b/code/game/objects/items/weapons/twohanded.dm @@ -101,7 +101,7 @@ w_class = SIZE_HUGE icon_state = "offhand" name = "offhand" - flags_item = DELONDROP|TWOHANDED|WIELDED + flags_item = DELONDROP|TWOHANDED|WIELDED|CANTSTRIP /obj/item/weapon/twohanded/offhand/unwield(mob/user) if(flags_item & WIELDED) diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm index 8e19a19053..b887cff60c 100644 --- a/code/modules/asset_cache/asset_list.dm +++ b/code/modules/asset_cache/asset_list.dm @@ -341,3 +341,24 @@ GLOBAL_LIST_EMPTY(asset_datums) if (!item_filename) return . = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename)) + +/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', + ) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 4ea2f35aa6..2a419eddf1 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -399,23 +399,6 @@ bodytemperature = max(bodytemperature, BODYTEMP_HEAT_DAMAGE_LIMIT+10) recalculate_move_delay = TRUE - -/mob/living/carbon/show_inv(mob/living/carbon/user as mob) - user.set_interaction(src) - var/dat = {" -
[name]
-

-
Head(Mask): [(wear_mask ? wear_mask : "Nothing")] -
Left Hand: [(l_hand ? l_hand : "Nothing")] -
Right Hand: [(r_hand ? r_hand : "Nothing")] -
Back: [(back ? back : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank) && !( internal )) ? " Set Internal" : "")] -
[(handcuffed ? "Handcuffed" : "Not Handcuffed")] -
[(internal ? "Remove Internal" : "")] -
Refresh -
Close -
"} - show_browser(user, dat, name, "mob[name]") - /** * Called by [/mob/dead/observer/proc/do_observe] when a carbon mob is observed by a ghost with [/datum/preferences/var/auto_observe] enabled. * diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 350bcec3e8..c394431c72 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -13,7 +13,7 @@ create_reagents(1000) if(!real_name || !name) change_real_name(src, "unknown") - + AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human, should_strip)) . = ..() prev_gender = gender // Debug for plural genders @@ -270,49 +270,6 @@ return TRUE return FALSE - - -/mob/living/carbon/human/show_inv(mob/living/user) - if(ismaintdrone(user)) - return - var/obj/item/clothing/under/suit = null - if(istype(w_uniform, /obj/item/clothing/under)) - suit = w_uniform - - user.set_interaction(src) - var/dat = {" -
[name]
-

-
(Exo)Suit: [(wear_suit ? wear_suit : "Nothing")] -
Suit Storage: [(s_store ? s_store : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(s_store, /obj/item/tank) && !( internal )) ? " Set Internal" : "")] -
Back: [(back ? back : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank) && !( internal )) ? " Set Internal" : "")] -
Head(Mask): [(wear_mask ? wear_mask : "Nothing")] -
Left Hand: [(l_hand ? l_hand : "Nothing")] -
Right Hand: [(r_hand ? r_hand : "Nothing")] -
Gloves: [(gloves ? gloves : "Nothing")] -
Eyes: [(glasses ? glasses : "Nothing")] -
Left Ear: [(wear_l_ear ? wear_l_ear : "Nothing")] -
Right Ear: [(wear_r_ear ? wear_r_ear : "Nothing")] -
Head: [(head ? head : "Nothing")] -
Shoes: [(shoes ? shoes : "Nothing")] -
Belt: [(belt ? belt : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(belt, /obj/item/tank) && !internal) ? " Set Internal" : "")] -
Uniform: [(w_uniform ? w_uniform : "Nothing")] [(suit) ? ((suit.has_sensor == UNIFORM_HAS_SENSORS) ? " Sensors" : "") : null] -
ID: [(wear_id ? wear_id : "Nothing")] -
Left Pocket: [(l_store ? l_store : "Nothing")] -
Right Pocket: [(r_store ? r_store : "Nothing")] -
- [handcuffed ? "
Handcuffed" : ""] - [legcuffed ? "
Legcuffed" : ""] - [suit && LAZYLEN(suit.accessories) ? "
Remove Accessory" : ""] - [internal ? "
Remove Internal" : ""] - [istype(wear_id, /obj/item/card/id/dogtag) ? "
Retrieve Info Tag" : ""] -
Remove Splints -
-
Refresh -
Close -
"} - show_browser(user, dat, name, "mob[name]") - /** * Handles any storage containers that the human is looking inside when auto-observed. */ @@ -428,9 +385,6 @@ /mob/living/carbon/human/Topic(href, href_list) - if(href_list["refresh"]) - if(interactee&&(in_range(src, usr))) - show_inv(interactee) if(href_list["mach_close"]) var/t1 = text("window=[]", href_list["mach_close"]) @@ -475,76 +429,6 @@ what = usr.get_active_hand() usr.stripPanelEquip(what,src,slot) - if(href_list["internal"]) - - if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr)) - attack_log += text("\[[time_stamp()]\] Has had their internals toggled by [key_name(usr)]") - usr.attack_log += text("\[[time_stamp()]\] Attempted to toggle [key_name(src)]'s' internals") - if(internal) - usr.visible_message(SPAN_DANGER("[usr] is trying to disable [src]'s internals"), null, null, 3) - else - usr.visible_message(SPAN_DANGER("[usr] is trying to enable [src]'s internals."), null, null, 3) - - if(do_after(usr, POCKET_STRIP_DELAY, INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) - if(internal) - internal.add_fingerprint(usr) - internal = null - visible_message("[src] is no longer running on internals.", null, null, 1) - else - if(istype(wear_mask, /obj/item/clothing/mask)) - if(istype(back, /obj/item/tank)) - internal = back - else if(istype(s_store, /obj/item/tank)) - internal = s_store - else if(istype(belt, /obj/item/tank)) - internal = belt - if(internal) - visible_message(SPAN_NOTICE("[src] is now running on internals."), null, null, 1) - internal.add_fingerprint(usr) - - // Update strip window - if(usr.interactee == src && Adjacent(usr)) - show_inv(usr) - - - if(href_list["splints"]) - if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr)) - if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (stat == DEAD || health < HEALTH_THRESHOLD_CRIT) && !get_target_lock(usr.faction_group)) - to_chat(usr, SPAN_WARNING("You can't strip a crit or dead member of another faction!")) - return - attack_log += text("\[[time_stamp()]\] Has had their splints removed by [key_name(usr)]") - usr.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(src)]'s' splints ") - remove_splints(usr) - - if(href_list["tie"]) - if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr)) - if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (stat == DEAD || health < HEALTH_THRESHOLD_CRIT) && !get_target_lock(usr.faction_group)) - to_chat(usr, SPAN_WARNING("You can't strip a crit or dead member of another faction!")) - return - if(w_uniform && istype(w_uniform, /obj/item/clothing)) - var/obj/item/clothing/under/U = w_uniform - if(!LAZYLEN(U.accessories)) - return FALSE - var/obj/item/clothing/accessory/A = LAZYACCESS(U.accessories, 1) - if(LAZYLEN(U.accessories) > 1) - A = tgui_input_list(usr, "Select an accessory to remove from [U]", "Remove accessory", U.accessories) - if(!istype(A)) - return - attack_log += text("\[[time_stamp()]\] Has had their accessory ([A]) removed by [key_name(usr)]") - usr.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(src)]'s' accessory ([A])") - if(istype(A, /obj/item/clothing/accessory/holobadge) || istype(A, /obj/item/clothing/accessory/medal)) - visible_message(SPAN_DANGER("[usr] tears off \the [A] from [src]'s [U]!"), null, null, 5) - if(U == w_uniform) - U.remove_accessory(usr, A) - else - if(HAS_TRAIT(src, TRAIT_UNSTRIPPABLE) && !is_mob_incapacitated()) //Can't strip the unstrippable! - to_chat(usr, SPAN_DANGER("[src] has an unbreakable grip on their equipment!")) - return - visible_message(SPAN_DANGER("[usr] is trying to take off \a [A] from [src]'s [U]!"), null, null, 5) - if(do_after(usr, get_strip_delay(usr, src), INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) - if(U == w_uniform) - U.remove_accessory(usr, A) - if(href_list["sensor"]) if(!usr.action_busy && !usr.is_mob_incapacitated() && Adjacent(usr)) if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (stat == DEAD || health < HEALTH_THRESHOLD_CRIT) && !get_target_lock(usr.faction_group)) 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..fbf6fb5ce4 --- /dev/null +++ b/code/modules/mob/living/carbon/human/human_stripping.dm @@ -0,0 +1,272 @@ +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/eyes, + /datum/strippable_item/mob_item_slot/r_ear, + /datum/strippable_item/mob_item_slot/l_ear, + /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/mob_item_slot/hand/left, + /datum/strippable_item/mob_item_slot/hand/right, + /datum/strippable_item/mob_item_slot/cuffs/handcuffs, + /datum/strippable_item/mob_item_slot/cuffs/legcuffs, +))) + +/mob/living/carbon/human/proc/should_strip(mob/user) + if (user.pulling == src && user.grab_level == GRAB_AGGRESSIVE && (user.a_intent & INTENT_GRAB)) + return FALSE //to not interfere with fireman carry + return TRUE + +/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_FACE + +/datum/strippable_item/mob_item_slot/mask/get_alternate_action(atom/source, mob/user) + var/obj/item/clothing/mask = get_item(source) + if (!istype(mask)) + return + if (!ishuman(source)) + return + var/mob/living/carbon/human/sourcehuman = source + if (istype(sourcehuman.s_store, /obj/item/tank)) + return "toggle_internals" + if (istype(sourcehuman.back, /obj/item/tank)) + return "toggle_internals" + if (istype(sourcehuman.belt, /obj/item/tank)) + return "toggle_internals" + return + +/datum/strippable_item/mob_item_slot/mask/alternate_action(atom/source, mob/user) + if(!ishuman(source)) + return + var/mob/living/carbon/human/sourcehuman = source + if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user)) + return + if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcehuman.stat == DEAD || sourcehuman.health < HEALTH_THRESHOLD_CRIT) && !sourcehuman.get_target_lock(user.faction_group)) + to_chat(user, SPAN_WARNING("You can't toggle internals of a crit or dead member of another faction!")) + return + + sourcehuman.attack_log += text("\[[time_stamp()]\] Has had their internals toggled by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] Attempted to toggle [key_name(src)]'s' internals") + if(sourcehuman.internal) + user.visible_message(SPAN_DANGER("[user] is trying to disable [sourcehuman]'s internals"), null, null, 3) + else + user.visible_message(SPAN_DANGER("[user] is trying to enable [sourcehuman]'s internals."), null, null, 3) + + if(!do_after(user, POCKET_STRIP_DELAY, INTERRUPT_ALL, BUSY_ICON_GENERIC, sourcehuman, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) + return + + if(sourcehuman.internal) + sourcehuman.internal.add_fingerprint(user) + sourcehuman.internal = null + sourcehuman.visible_message("[sourcehuman] is no longer running on internals.", max_distance = 1) + return + + if(!istype(sourcehuman.wear_mask, /obj/item/clothing/mask)) + return + + if(istype(sourcehuman.back, /obj/item/tank)) + sourcehuman.internal = sourcehuman.back + else if(istype(sourcehuman.s_store, /obj/item/tank)) + sourcehuman.internal = sourcehuman.s_store + else if(istype(sourcehuman.belt, /obj/item/tank)) + sourcehuman.internal = sourcehuman.belt + + if(!sourcehuman.internal) + return + + sourcehuman.visible_message(SPAN_NOTICE("[sourcehuman] is now running on internals."), max_distance = 1) + sourcehuman.internal.add_fingerprint(user) + +/datum/strippable_item/mob_item_slot/eyes + key = STRIPPABLE_ITEM_EYES + item_slot = SLOT_EYES + +/datum/strippable_item/mob_item_slot/r_ear + key = STRIPPABLE_ITEM_R_EAR + item_slot = SLOT_EAR + +/datum/strippable_item/mob_item_slot/l_ear + key = STRIPPABLE_ITEM_L_EAR + item_slot = SLOT_EAR + +/datum/strippable_item/mob_item_slot/jumpsuit + key = STRIPPABLE_ITEM_JUMPSUIT + item_slot = SLOT_ICLOTHING + +/datum/strippable_item/mob_item_slot/jumpsuit/get_alternate_action(atom/source, mob/user) + var/obj/item/clothing/under/uniform = get_item(source) + if (!istype(uniform)) + return null + return uniform?.accessories ? "remove_accessory" : null + +/datum/strippable_item/mob_item_slot/jumpsuit/alternate_action(atom/source, mob/user) + if(!ishuman(source)) + return + var/mob/living/carbon/human/sourcemob = source + if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user)) + return + if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcemob.stat == DEAD || sourcemob.health < HEALTH_THRESHOLD_CRIT) && !sourcemob.get_target_lock(user.faction_group)) + to_chat(user, SPAN_WARNING("You can't strip a crit or dead member of another faction!")) + return + if(!sourcemob.w_uniform || !istype(sourcemob.w_uniform, /obj/item/clothing)) + return + + var/obj/item/clothing/under/uniform = sourcemob.w_uniform + if(!LAZYLEN(uniform.accessories)) + return FALSE + var/obj/item/clothing/accessory/accessory = LAZYACCESS(uniform.accessories, 1) + if(LAZYLEN(uniform.accessories) > 1) + accessory = tgui_input_list(user, "Select an accessory to remove from [uniform]", "Remove accessory", uniform.accessories) + if(!istype(accessory)) + return + sourcemob.attack_log += text("\[[time_stamp()]\] Has had their accessory ([accessory]) removed by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(sourcemob)]'s' accessory ([accessory])") + if(istype(accessory, /obj/item/clothing/accessory/holobadge) || istype(accessory, /obj/item/clothing/accessory/medal)) + sourcemob.visible_message(SPAN_DANGER("[user] tears off [accessory] from [sourcemob]'s [uniform]!"), null, null, 5) + if(uniform == sourcemob.w_uniform) + uniform.remove_accessory(user, accessory) + return + + if(HAS_TRAIT(sourcemob, TRAIT_UNSTRIPPABLE) && !sourcemob.is_mob_incapacitated()) //Can't strip the unstrippable! + to_chat(user, SPAN_DANGER("[sourcemob] has an unbreakable grip on their equipment!")) + return + sourcemob.visible_message(SPAN_DANGER("[user] is trying to take off \a [accessory] from [source]'s [uniform]!"), null, null, 5) + + if(!do_after(user, sourcemob.get_strip_delay(user, sourcemob), INTERRUPT_ALL, BUSY_ICON_GENERIC, sourcemob, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) + return + + if(uniform != sourcemob.w_uniform) + return + + uniform.remove_accessory(user, accessory) + +/datum/strippable_item/mob_item_slot/suit + key = STRIPPABLE_ITEM_SUIT + item_slot = SLOT_OCLOTHING + +/datum/strippable_item/mob_item_slot/suit/has_no_item_alt_action() + return TRUE + +/datum/strippable_item/mob_item_slot/suit/get_alternate_action(atom/source, mob/user) + if(!ishuman(source)) + return + var/mob/living/carbon/human/sourcemob = source + for(var/bodypart in list("l_leg","r_leg","l_arm","r_arm","r_hand","l_hand","r_foot","l_foot","chest","head","groin")) + var/obj/limb/limb = sourcemob.get_limb(bodypart) + if(limb && (limb.status & LIMB_SPLINTED)) + return "remove_splints" + return + +/datum/strippable_item/mob_item_slot/suit/alternate_action(atom/source, mob/user) + if(!ishuman(source)) + return + var/mob/living/carbon/human/sourcemob = source + if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user)) + return + if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcemob.stat == DEAD || sourcemob.health < HEALTH_THRESHOLD_CRIT) && !sourcemob.get_target_lock(user.faction_group)) + to_chat(user, SPAN_WARNING("You can't remove splints of a crit or dead member of another faction!")) + return + sourcemob.attack_log += text("\[[time_stamp()]\] Has had their splints removed by [key_name(user)]") + user.attack_log += text("\[[time_stamp()]\] Attempted to remove [key_name(sourcemob)]'s' splints ") + sourcemob.remove_splints(user) + +/datum/strippable_item/mob_item_slot/gloves + key = STRIPPABLE_ITEM_GLOVES + item_slot = SLOT_HANDS + +/datum/strippable_item/mob_item_slot/feet + key = STRIPPABLE_ITEM_FEET + item_slot = SLOT_FEET + +/datum/strippable_item/mob_item_slot/suit_storage + key = STRIPPABLE_ITEM_SUIT_STORAGE + item_slot = SLOT_SUIT_STORE + +/datum/strippable_item/mob_item_slot/id + key = STRIPPABLE_ITEM_ID + item_slot = SLOT_ID + +/datum/strippable_item/mob_item_slot/id/get_alternate_action(atom/source, mob/user) + var/obj/item/card/id/dogtag/tag = get_item(source) + if(!ishuman(source)) + return + var/mob/living/carbon/human/sourcemob = source + if (!istype(tag)) + return + if (!sourcemob.undefibbable && (!skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED) || sourcemob.stat != DEAD)) + return + return tag.dogtag_taken ? null : "retrieve_tag" + +/datum/strippable_item/mob_item_slot/id/alternate_action(atom/source, mob/user) + if(!ishuman(source)) + return + var/mob/living/carbon/human/sourcemob = source + if(user.action_busy || user.is_mob_incapacitated() || !source.Adjacent(user)) + return + if(MODE_HAS_TOGGLEABLE_FLAG(MODE_NO_STRIPDRAG_ENEMY) && (sourcemob.stat == DEAD || sourcemob.health < HEALTH_THRESHOLD_CRIT) && !sourcemob.get_target_lock(user.faction_group)) + to_chat(user, SPAN_WARNING("You can't strip a crit or dead member of another faction!")) + return + if(!istype(sourcemob.wear_id, /obj/item/card/id/dogtag)) + return + if (!sourcemob.undefibbable && !skillcheck(user, SKILL_POLICE, SKILL_POLICE_SKILLED)) + return + var/obj/item/card/id/dogtag/tag = sourcemob.wear_id + if(tag.dogtag_taken) + to_chat(user, SPAN_WARNING("Someone's already taken [sourcemob]'s information tag.")) + return + + if(sourcemob.stat != DEAD) + to_chat(user, SPAN_WARNING("You can't take a dogtag's information tag while its owner is alive.")) + return + + to_chat(user, SPAN_NOTICE("You take [sourcemob]'s information tag, leaving the ID tag")) + tag.dogtag_taken = TRUE + tag.icon_state = "dogtag_taken" + var/obj/item/dogtag/newtag = new(sourcemob.loc) + newtag.fallen_names = list(tag.registered_name) + newtag.fallen_assgns = list(tag.assignment) + newtag.fallen_blood_types = list(tag.blood_type) + user.put_in_hands(newtag) + + + +/datum/strippable_item/mob_item_slot/belt + key = STRIPPABLE_ITEM_BELT + item_slot = SLOT_WAIST + +/datum/strippable_item/mob_item_slot/pocket/left + key = STRIPPABLE_ITEM_LPOCKET + item_slot = SLOT_STORE + +/datum/strippable_item/mob_item_slot/pocket/right + key = STRIPPABLE_ITEM_RPOCKET + item_slot = SLOT_STORE + +/datum/strippable_item/mob_item_slot/hand/left + key = STRIPPABLE_ITEM_LHAND + +/datum/strippable_item/mob_item_slot/hand/right + key = STRIPPABLE_ITEM_RHAND + +/datum/strippable_item/mob_item_slot/cuffs/handcuffs + key = STRIPPABLE_ITEM_HANDCUFFS + +/datum/strippable_item/mob_item_slot/cuffs/legcuffs + key = STRIPPABLE_ITEM_LEGCUFFS diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 22deabce45..871a2cd376 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -504,68 +504,6 @@ /// Final result is overall delay * speed multiplier return target_delay * user_speed -/mob/living/carbon/human/stripPanelUnequip(obj/item/interact_item, mob/target_mob, slot_to_process) - if(HAS_TRAIT(target_mob, TRAIT_UNSTRIPPABLE) && !target_mob.is_mob_incapacitated()) //Can't strip the unstrippable! - to_chat(src, SPAN_DANGER("[target_mob] has an unbreakable grip on their equipment!")) - return - if(interact_item.flags_item & ITEM_ABSTRACT) - return - if(interact_item.flags_item & NODROP) - to_chat(src, SPAN_WARNING("You can't remove \the [interact_item.name], it appears to be stuck!")) - return - if(interact_item.flags_inventory & CANTSTRIP) - to_chat(src, SPAN_WARNING("You're having difficulty removing \the [interact_item.name].")) - return - target_mob.attack_log += "\[[time_stamp()]\] Has had their [interact_item.name] ([slot_to_process]) attempted to be removed by [key_name(src)]" - attack_log += "\[[time_stamp()]\] Attempted to remove [key_name(target_mob)]'s [interact_item.name] ([slot_to_process])" - log_interact(src, target_mob, "[key_name(src)] tried to remove [key_name(target_mob)]'s [interact_item.name] ([slot_to_process]).") - - src.visible_message(SPAN_DANGER("[src] tries to remove [target_mob]'s [interact_item.name]."), \ - SPAN_DANGER("You are trying to remove [target_mob]'s [interact_item.name]."), null, 5) - interact_item.add_fingerprint(src) - if(do_after(src, get_strip_delay(src, target_mob), INTERRUPT_ALL, BUSY_ICON_GENERIC, target_mob, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) - if(interact_item && Adjacent(target_mob) && interact_item == target_mob.get_item_by_slot(slot_to_process)) - target_mob.drop_inv_item_on_ground(interact_item) - log_interact(src, target_mob, "[key_name(src)] removed [key_name(target_mob)]'s [interact_item.name] ([slot_to_process]) successfully.") - - if(target_mob) - if(interactee == target_mob && Adjacent(target_mob)) - target_mob.show_inv(src) - - -/mob/living/carbon/human/stripPanelEquip(obj/item/interact_item, mob/target_mob, slot_to_process) - if(HAS_TRAIT(target_mob, TRAIT_UNSTRIPPABLE) && !target_mob.is_mob_incapacitated()) - to_chat(src, SPAN_DANGER("[target_mob] is too strong to force [interact_item.name] onto them!")) - return - if(interact_item && !(interact_item.flags_item & ITEM_ABSTRACT)) - if(interact_item.flags_item & NODROP) - to_chat(src, SPAN_WARNING("You can't put \the [interact_item.name] on [target_mob], it's stuck to your hand!")) - return - if(interact_item.flags_inventory & CANTSTRIP) - to_chat(src, SPAN_WARNING("You're having difficulty putting \the [interact_item.name] on [target_mob].")) - return - if(interact_item.flags_item & WIELDED) - interact_item.unwield(src) - if(!interact_item.mob_can_equip(target_mob, slot_to_process, TRUE)) - to_chat(src, SPAN_WARNING("You can't put \the [interact_item.name] on [target_mob]!")) - return - visible_message(SPAN_NOTICE("[src] tries to put \the [interact_item.name] on [target_mob]."), null, null, 5) - if(do_after(src, get_strip_delay(src, target_mob), INTERRUPT_ALL, BUSY_ICON_GENERIC, target_mob, INTERRUPT_MOVED, BUSY_ICON_GENERIC)) - if(interact_item == get_active_hand() && !target_mob.get_item_by_slot(slot_to_process) && Adjacent(target_mob)) - if(interact_item.flags_item & WIELDED) //to prevent re-wielding it during the do_after - interact_item.unwield(src) - if(interact_item.mob_can_equip(target_mob, slot_to_process, TRUE))//Placing an item on the mob - drop_inv_item_on_ground(interact_item) - if(interact_item && !QDELETED(interact_item)) //Might be self-deleted? - target_mob.equip_to_slot_if_possible(interact_item, slot_to_process, 1, 0, 1, 1) - if(ishuman(target_mob) && target_mob.stat == DEAD) - var/mob/living/carbon/human/human_target = target_mob - human_target.disable_lights() // take that powergamers -spookydonut - - if(target_mob) - if(interactee == target_mob && Adjacent(target_mob)) - target_mob.show_inv(src) - /mob/living/carbon/human/drop_inv_item_on_ground(obj/item/I, nomoveupdate, force) remember_dropped_object(I) return ..() diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 8d3367ee68..d00b08f183 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -261,10 +261,6 @@ move_delay = . - -/mob/living/carbon/xenomorph/show_inv(mob/user) - return - /mob/living/carbon/xenomorph/proc/pounced_mob(mob/living/L) // This should only be called back by a mob that has pounce, so no need to check var/datum/action/xeno_action/activable/pounce/pounceAction = get_xeno_action_by_type(src, /datum/action/xeno_action/activable/pounce) diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index f1452d95c2..78c96bbbb7 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -110,19 +110,6 @@ /* * Inventory */ -/mob/living/simple_animal/parrot/show_inv(mob/user as mob) - user.set_interaction(src) - if(user.stat) return - - var/dat = "
Inventory of [name]

" - if(ears) - dat += "
Headset: [ears] (Remove)" - else - dat += "
Headset: Nothing" - - user << browse(dat, text("window=mob[];size=325x500", name)) - onclose(user, "mob[real_name]") - return /mob/living/simple_animal/parrot/Topic(href, href_list) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c2cf6402ed..13c47719a7 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -372,25 +372,6 @@ SIGNAL_HANDLER reset_view(null) -/mob/proc/show_inv(mob/user) - user.set_interaction(src) - var/dat = {" -


[name] -

-
Head(Mask): [(wear_mask ? wear_mask : "Nothing")] -
Left Hand: [(l_hand ? l_hand : "Nothing")] -
Right Hand: [(r_hand ? r_hand : "Nothing")] -
Back: [(back ? back : "Nothing")] [((istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank) && !( internal )) ? text(" Set Internal", src) : "")] -
[(internal ? text("Remove Internal") : "")] -
Empty Pockets -
Refresh -
Close -
"} - show_browser(user, dat, name, "mob[name]") - return - - - /mob/proc/point_to_atom(atom/A, turf/T) //Squad Leaders and above have reduced cooldown and get a bigger arrow if(check_improved_pointing()) @@ -448,21 +429,6 @@ update_flavor_text() return - -/mob/MouseDrop(mob/M) - ..() - if(M != usr) return - if(usr == src) return - if(!Adjacent(usr)) return - if(!ishuman(M) && !ismonkey(M)) return - if(!ishuman(src) && !ismonkey(src)) return - if(M.is_mob_incapacitated()) - return - if(M.pulling == src && (M.a_intent & INTENT_GRAB) && M.grab_level == GRAB_AGGRESSIVE) - return - - show_inv(M) - /mob/proc/swap_hand() hand = !hand diff --git a/code/modules/projectiles/gun_attachables.dm b/code/modules/projectiles/gun_attachables.dm index 0c6da6f973..6f972bdeb8 100644 --- a/code/modules/projectiles/gun_attachables.dm +++ b/code/modules/projectiles/gun_attachables.dm @@ -2445,7 +2445,7 @@ Defined in conflicts.dm of the #defines folder. /obj/item/attachable/stock/smg/collapsible/brace/apply_on_weapon(obj/item/weapon/gun/G) if(stock_activated) - G.flags_item |= NODROP + G.flags_item |= NODROP|FORCEDROP_CONDITIONAL accuracy_mod = -HIT_ACCURACY_MULT_TIER_3 scatter_mod = SCATTER_AMOUNT_TIER_8 recoil_mod = RECOIL_AMOUNT_TIER_2 //Hurts pretty bad if it's wielded. @@ -2456,7 +2456,7 @@ Defined in conflicts.dm of the #defines folder. icon_state = "smg_brace_on" attach_icon = "smg_brace_a_on" else - G.flags_item &= ~NODROP + G.flags_item &= ~(NODROP|FORCEDROP_CONDITIONAL) accuracy_mod = 0 scatter_mod = 0 recoil_mod = 0 diff --git a/colonialmarines.dme b/colonialmarines.dme index d5e035d6d2..fa6152a276 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -101,6 +101,7 @@ #include "code\__DEFINES\stamina.dm" #include "code\__DEFINES\stats.dm" #include "code\__DEFINES\status_effects.dm" +#include "code\__DEFINES\strippable.dm" #include "code\__DEFINES\STUI.dm" #include "code\__DEFINES\subsystems.dm" #include "code\__DEFINES\surgery.dm" @@ -481,6 +482,7 @@ #include "code\datums\elements\light_blocking.dm" #include "code\datums\elements\mouth_drop_item.dm" #include "code\datums\elements\poor_eyesight_correction.dm" +#include "code\datums\elements\strippable.dm" #include "code\datums\elements\suturing.dm" #include "code\datums\elements\yautja_tracked_item.dm" #include "code\datums\elements\bullet_trait\damage_boost.dm" @@ -1911,6 +1913,7 @@ #include "code\modules\mob\living\carbon\human\human_dummy.dm" #include "code\modules\mob\living\carbon\human\human_helpers.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/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/tgui/packages/tgui/interfaces/StripMenu.tsx b/tgui/packages/tgui/interfaces/StripMenu.tsx new file mode 100644 index 0000000000..acb4086c82 --- /dev/null +++ b/tgui/packages/tgui/interfaces/StripMenu.tsx @@ -0,0 +1,382 @@ +import { range } from 'common/collections'; +import { BooleanLike } from 'common/react'; + +import { resolveAsset } from '../assets'; +import { useBackend } from '../backend'; +import { Box, Button, Icon, Stack } from '../components'; +import { Window } from '../layouts'; + +const ROWS = 5; +const COLUMNS = 6; + +const BUTTON_DIMENSIONS = '50px'; + +type GridSpotKey = string; + +const getGridSpotKey = (spot: [number, number]): GridSpotKey => { + return `${spot[0]}/${spot[1]}`; +}; + +const CornerText = (props: { + readonly align: 'left' | 'right'; + readonly children: string; +}): JSX.Element => { + const { align, children } = props; + + return ( + + {children} + + ); +}; + +type AlternateAction = { + icon: string; + text: string; +}; + +const ALTERNATE_ACTIONS: Record = { + remove_splints: { + icon: 'crutch', + text: 'Remove splints', + }, + + remove_accessory: { + icon: 'tshirt', + text: 'Remove accessory', + }, + + retrieve_tag: { + icon: 'tags', + text: 'Retrieve info tag', + }, + + toggle_internals: { + icon: 'mask-face', + text: 'Toggle internals', + }, +}; + +type Slot = { + displayName: string; + gridSpot: GridSpotKey; + image?: string; + additionalComponent?: JSX.Element; + hideEmpty?: boolean; +}; + +const SLOTS: Record = { + glasses: { + displayName: 'glasses', + gridSpot: getGridSpotKey([0, 1]), + image: 'inventory-glasses.png', + }, + + head: { + displayName: 'headwear', + gridSpot: getGridSpotKey([0, 2]), + image: 'inventory-head.png', + }, + + wear_mask: { + displayName: 'mask', + gridSpot: getGridSpotKey([1, 2]), + image: 'inventory-mask.png', + }, + + wear_r_ear: { + displayName: 'right earwear', + gridSpot: getGridSpotKey([0, 3]), + image: 'inventory-ears.png', + }, + + wear_l_ear: { + displayName: 'left earwear', + gridSpot: getGridSpotKey([1, 3]), + image: 'inventory-ears.png', + }, + + handcuffs: { + displayName: 'handcuffs', + gridSpot: getGridSpotKey([1, 4]), + hideEmpty: true, + }, + + legcuffs: { + displayName: 'legcuffs', + gridSpot: getGridSpotKey([1, 5]), + hideEmpty: true, + }, + + w_uniform: { + displayName: 'uniform', + gridSpot: getGridSpotKey([2, 1]), + image: 'inventory-uniform.png', + }, + + wear_suit: { + displayName: 'suit', + gridSpot: getGridSpotKey([2, 2]), + image: 'inventory-suit.png', + }, + + gloves: { + displayName: 'gloves', + gridSpot: getGridSpotKey([2, 3]), + image: 'inventory-gloves.png', + }, + + r_hand: { + displayName: 'right hand', + gridSpot: getGridSpotKey([2, 4]), + image: 'inventory-hand_r.png', + additionalComponent: R, + }, + + l_hand: { + displayName: 'left hand', + gridSpot: getGridSpotKey([2, 5]), + image: 'inventory-hand_l.png', + additionalComponent: L, + }, + + shoes: { + displayName: 'shoes', + gridSpot: getGridSpotKey([3, 2]), + image: 'inventory-shoes.png', + }, + + j_store: { + displayName: 'suit storage item', + gridSpot: getGridSpotKey([4, 0]), + image: 'inventory-suit_storage.png', + }, + + id: { + displayName: 'ID', + gridSpot: getGridSpotKey([4, 1]), + image: 'inventory-id.png', + }, + + belt: { + displayName: 'belt', + gridSpot: getGridSpotKey([4, 2]), + image: 'inventory-belt.png', + }, + + back: { + displayName: 'backpack', + gridSpot: getGridSpotKey([4, 3]), + image: 'inventory-back.png', + }, + + l_store: { + displayName: 'left pocket', + gridSpot: getGridSpotKey([4, 4]), + image: 'inventory-pocket.png', + }, + + r_store: { + displayName: 'right pocket', + gridSpot: getGridSpotKey([4, 5]), + image: 'inventory-pocket.png', + }, +}; + +enum ObscuringLevel { + Completely = 1, + Hidden = 2, +} + +type Interactable = { + interacting: BooleanLike; +}; + +/** + * Some possible options: + * + * null - No interactions, no item, but is an available slot + * { interacting: 1 } - No item, but we're interacting with it + * { icon: icon, name: name } - An item with no alternate actions + * that we're not interacting with. + * { icon, name, interacting: 1 } - An item with no alternate actions + * that we're interacting with. + */ +type StripMenuItem = + | null + | Interactable + | (( + | { + icon: string; + name: string; + alternate: string; + } + | { + obscured: ObscuringLevel; + } + | { + no_item_action: string; + } + ) & + Partial); + +type StripMenuData = { + items: Record; + name: string; +}; + +const StripContent = (props: { readonly item: StripMenuItem }) => { + if (props.item && 'name' in props.item) { + return ( + + ); + } + if (props.item && 'obscured' in props.item) { + return ( + + ); + } + return <> ; +}; + +export const StripMenu = (props, context) => { + const { act, data } = useBackend(); + + const gridSpots = new Map(); + for (const key of Object.keys(data.items)) { + const item = data.items[key]; + if (item === null && SLOTS[key].hideEmpty) continue; + gridSpots.set(SLOTS[key].gridSpot, key); + } + + return ( + + + + {range(0, ROWS).map((row) => ( + + + {range(0, COLUMNS).map((column) => { + const key = getGridSpotKey([row, column]); + const keyAtSpot = gridSpots.get(key); + + if (!keyAtSpot) { + return ( + + ); + } + + const item = data.items[keyAtSpot]; + const slot = SLOTS[keyAtSpot]; + + let alternateAction: AlternateAction | undefined; + + let content; + let tooltip; + + if (item === null) { + tooltip = slot.displayName; + } else if ('name' in item) { + alternateAction = ALTERNATE_ACTIONS[item.alternate]; + tooltip = item.name; + } else if ('obscured' in item) { + tooltip = `obscured ${slot.displayName}`; + } else if ('no_item_action' in item) { + tooltip = slot.displayName; + alternateAction = ALTERNATE_ACTIONS[item.no_item_action]; + } + + return ( + + + + + {alternateAction !== undefined && ( + + )} + + + ); + })} + + + ))} + + + + ); +}; diff --git a/tgui/packages/tgui/styles/interfaces/StripMenu.scss b/tgui/packages/tgui/styles/interfaces/StripMenu.scss new file mode 100644 index 0000000000..d2c867c0ac --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/StripMenu.scss @@ -0,0 +1,65 @@ +@use '../base.scss'; + +$background-color: rgba(0, 0, 0, 0.33) !default; + +.StripMenu__cornertext_left { + position: relative; + left: 2px; + text-align: left; + text-shadow: 1px 1px 1px #555; +} + +.StripMenu__cornertext_right { + position: relative; + left: -2px; + text-align: right; + text-shadow: 1px 1px 1px #555; +} + +.StripMenu__iconbox { + height: 100%; + width: 100%; + -ms-interpolation-mode: nearest-neighbor; + vertical-align: middle; +} + +.StripMenu__obscured { + text-align: center; + height: 100%; + width: 100%; +} + +.StripMenu__itembox { + position: relative; + width: 100%; + height: 100%; +} + +.StripMenu__contentbox { + position: relative; +} + +.StripMenu__itembutton { + position: relative; + width: 100%; + height: 100%; + padding: 0; +} + +.StripMenu__itemslot { + position: absolute; + width: 32px; + height: 32px; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%) scale(0.8); + opacity: 0.7; +} + +.StripMenu__alternativeaction { + background: rgba(0, 0, 0, 0.6); + position: absolute; + bottom: 0; + right: 0; + z-index: 2; +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index b51e602746..639365e0e3 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -80,6 +80,7 @@ @include meta.load-css('./interfaces/common/Dpad.scss'); @include meta.load-css('./interfaces/common/ElectricalPanel.scss'); @include meta.load-css('./interfaces/TacticalMap.scss'); +@include meta.load-css('./interfaces/StripMenu.scss'); // Layouts @include meta.load-css('./layouts/Layout.scss'); From 0aef8231bbcb46342cf957431f288b8c42d35928 Mon Sep 17 00:00:00 2001 From: private-tristan Date: Mon, 19 Aug 2024 20:57:03 -0400 Subject: [PATCH 2/2] port the latest version to maybe fix linters thanks Doubleumc for help --- tgui/packages/tgui/interfaces/StripMenu.tsx | 26 ++++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tgui/packages/tgui/interfaces/StripMenu.tsx b/tgui/packages/tgui/interfaces/StripMenu.tsx index acb4086c82..6790d17a29 100644 --- a/tgui/packages/tgui/interfaces/StripMenu.tsx +++ b/tgui/packages/tgui/interfaces/StripMenu.tsx @@ -3,13 +3,13 @@ import { BooleanLike } from 'common/react'; import { resolveAsset } from '../assets'; import { useBackend } from '../backend'; -import { Box, Button, Icon, Stack } from '../components'; +import { Box, Button, Icon, Image, Stack } from '../components'; import { Window } from '../layouts'; const ROWS = 5; const COLUMNS = 6; -const BUTTON_DIMENSIONS = '50px'; +const BUTTON_DIMENSIONS = '64px'; type GridSpotKey = string; @@ -234,11 +234,11 @@ type StripMenuData = { const StripContent = (props: { readonly item: StripMenuItem }) => { if (props.item && 'name' in props.item) { return ( - ); @@ -261,7 +261,7 @@ const StripContent = (props: { readonly item: StripMenuItem }) => { return <> ; }; -export const StripMenu = (props, context) => { +export const StripMenu = (props) => { const { act, data } = useBackend(); const gridSpots = new Map(); @@ -272,7 +272,7 @@ export const StripMenu = (props, context) => { } return ( - + {range(0, ROWS).map((row) => ( @@ -286,10 +286,8 @@ export const StripMenu = (props, context) => { return ( ); } @@ -345,9 +343,9 @@ export const StripMenu = (props, context) => { }} > {slot.image && ( - )}