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 += "
- 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)$t27X?8h5b
zeZ9z64Hu?sL|G%_j_g^bx7j-V=@Zp4;`>WFeebTcbvF1+{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<_mxAdQ#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#$b8)T=`G>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$j&pYAP5ZL7>=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|GjwUYBSaFnA@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');
|