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

Citadel Preferences

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

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

Inventory of [name]

" - dat += "
Headset: [ears]" : "add_inv=ears'>Nothing"]" +/datum/strippable_item/parrot_headset + key = STRIPPABLE_ITEM_PARROT_HEADSET - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "window=mob[REF(src)]") +/datum/strippable_item/parrot_headset/get_item(atom/source) + var/mob/living/simple_animal/parrot/parrot_source = source + return istype(parrot_source) ? parrot_source.ears : null +/datum/strippable_item/parrot_headset/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if (!.) + return FALSE -/mob/living/simple_animal/parrot/Topic(href, href_list) - if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=mob[REF(src)]") - usr.unset_machine() + if (!istype(equipping, /obj/item/radio/headset)) + to_chat(user, "[equipping] won't fit!") + return FALSE + + return TRUE + +// There is no delay for putting a headset on a parrot. +/datum/strippable_item/parrot_headset/start_equip(atom/source, obj/item/equipping, mob/user) + if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY) + return FALSE + return TRUE + +/datum/strippable_item/parrot_headset/finish_equip(atom/source, obj/item/equipping, mob/user) + if(!..()) + return FALSE + var/obj/item/radio/headset/radio = equipping + if (!istype(radio)) + return FALSE + + var/mob/living/simple_animal/parrot/parrot_source = source + if (!istype(parrot_source)) + return FALSE + + if (!user.transferItemToLoc(radio, source)) + return FALSE + + parrot_source.ears = radio + + to_chat(user, "You fit [radio] onto [source].") + + parrot_source.available_channels.Cut() + + for (var/channel in radio.channels) + var/channel_to_add + + switch (channel) + if (RADIO_CHANNEL_ENGINEERING) + channel_to_add = RADIO_TOKEN_ENGINEERING + if (RADIO_CHANNEL_COMMAND) + channel_to_add = RADIO_TOKEN_COMMAND + if (RADIO_CHANNEL_SECURITY) + channel_to_add = RADIO_TOKEN_SECURITY + if (RADIO_CHANNEL_SCIENCE) + channel_to_add = RADIO_TOKEN_SCIENCE + if (RADIO_CHANNEL_MEDICAL) + channel_to_add = RADIO_TOKEN_MEDICAL + if (RADIO_CHANNEL_SUPPLY) + channel_to_add = RADIO_TOKEN_SUPPLY + if (RADIO_CHANNEL_SERVICE) + channel_to_add = RADIO_TOKEN_SERVICE + + if (channel_to_add) + parrot_source.available_channels += channel_to_add + + if (radio.translate_binary) + parrot_source.available_channels.Add(MODE_TOKEN_BINARY) + +/datum/strippable_item/parrot_headset/start_unequip(atom/source, mob/user) + . = ..() + if (!.) + return FALSE + + var/mob/living/simple_animal/parrot/parrot_source = source + if (!istype(parrot_source)) return - //Removing from inventory - if(href_list["remove_inv"]) - var/remove_from = href_list["remove_inv"] - switch(remove_from) - if("ears") - if(!ears) - to_chat(usr, "There is nothing to remove from its [remove_from]!") - return - if(!stat) - say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - ears.forceMove(drop_location()) - ears = null - for(var/possible_phrase in speak) - if(copytext_char(possible_phrase, 2, 3) in GLOB.department_radio_keys) - possible_phrase = copytext_char(possible_phrase, 3) + if (!parrot_source.stat) + parrot_source.say("[parrot_source.available_channels.len ? "[pick(parrot_source.available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - //Adding things to inventory - else if(href_list["add_inv"]) - var/add_to = href_list["add_inv"] - if(!usr.get_active_held_item()) - to_chat(usr, "You have nothing in your hand to put on its [add_to]!") - return - switch(add_to) - if("ears") - if(ears) - to_chat(usr, "It's already wearing something!") - return - else - var/obj/item/item_to_add = usr.get_active_held_item() - if(!item_to_add) - return + return TRUE - if( !istype(item_to_add, /obj/item/radio/headset) ) - to_chat(usr, "This object won't fit!") - return - - var/obj/item/radio/headset/headset_to_add = item_to_add - - if(!usr.transferItemToLoc(headset_to_add, src)) - return - ears = headset_to_add - to_chat(usr, "You fit the headset onto [src].") - - clearlist(available_channels) - for(var/ch in headset_to_add.channels) - switch(ch) - if(RADIO_CHANNEL_ENGINEERING) - available_channels.Add(RADIO_TOKEN_ENGINEERING) - if(RADIO_CHANNEL_COMMAND) - available_channels.Add(RADIO_TOKEN_COMMAND) - if(RADIO_CHANNEL_SECURITY) - available_channels.Add(RADIO_TOKEN_SECURITY) - if(RADIO_CHANNEL_SCIENCE) - available_channels.Add(RADIO_TOKEN_SCIENCE) - if(RADIO_CHANNEL_MEDICAL) - available_channels.Add(RADIO_TOKEN_MEDICAL) - if(RADIO_CHANNEL_SUPPLY) - available_channels.Add(RADIO_TOKEN_SUPPLY) - if(RADIO_CHANNEL_SERVICE) - available_channels.Add(RADIO_TOKEN_SERVICE) - - if(headset_to_add.translate_binary) - available_channels.Add(MODE_TOKEN_BINARY) - else - return ..() +/datum/strippable_item/parrot_headset/finish_unequip(atom/source, mob/user) + ..() + var/mob/living/simple_animal/parrot/parrot_source = source + if (!istype(parrot_source)) + return + finish_unequip_mob(parrot_source.ears, parrot_source, user) + parrot_source.ears = null /* * Attack responces diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 8a71bb72c4..d240f97a23 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -289,9 +289,6 @@ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE, A) return TRUE -/mob/proc/show_inv(mob/user) - return - //view() but with a signal, to allow blacklisting some of the otherwise visible atoms. /mob/proc/fov_view(dist = world.view) . = view(dist, src) @@ -508,10 +505,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) unset_machine() src << browse(null, t1) - if(href_list["refresh"]) - if(machine && in_range(src, usr)) - show_inv(machine) - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) if(href_list["item"]) var/slot = text2num(href_list["item"]) @@ -528,12 +521,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) else usr.stripPanelEquip(what,src,slot) - if(usr.machine == src) - if(Adjacent(usr)) - show_inv(usr) - else - usr << browse(null,"window=mob[REF(src)]") - // The src mob is trying to strip an item from someone // Defined in living.dm /mob/proc/stripPanelUnequip(obj/item/what, mob/who) @@ -555,12 +542,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) if(isAI(M)) return -/mob/MouseDrop_T(atom/dropping, atom/user) - . = ..() - if(ismob(dropping) && dropping != user) - var/mob/M = dropping - M.show_inv(user) - /mob/proc/is_muzzled() return FALSE diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index eab1c55ca9..75cf6594e6 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -1010,7 +1010,7 @@ GLOBAL_LIST_EMPTY(vending_products) if(base64_cache[T.type]) base64 = base64_cache[T.type] else - base64 = icon2base64(icon(T.icon, T.icon_state)) + base64 = icon2base64(icon(T.icon, T.icon_state, SOUTH, 1)) base64_cache[T.type] = base64 break var/list/data = list( diff --git a/icons/UI_Icons/inventory/back.png b/icons/UI_Icons/inventory/back.png new file mode 100644 index 0000000000..736b9d64bf Binary files /dev/null and b/icons/UI_Icons/inventory/back.png differ diff --git a/icons/UI_Icons/inventory/belt.png b/icons/UI_Icons/inventory/belt.png new file mode 100644 index 0000000000..1be89d450a Binary files /dev/null and b/icons/UI_Icons/inventory/belt.png differ diff --git a/icons/UI_Icons/inventory/collar.png b/icons/UI_Icons/inventory/collar.png new file mode 100644 index 0000000000..71803b1b6c Binary files /dev/null and b/icons/UI_Icons/inventory/collar.png differ diff --git a/icons/UI_Icons/inventory/ears.png b/icons/UI_Icons/inventory/ears.png new file mode 100644 index 0000000000..e9a8f3c23c Binary files /dev/null and b/icons/UI_Icons/inventory/ears.png differ diff --git a/icons/UI_Icons/inventory/glasses.png b/icons/UI_Icons/inventory/glasses.png new file mode 100644 index 0000000000..6e6f1ad098 Binary files /dev/null and b/icons/UI_Icons/inventory/glasses.png differ diff --git a/icons/UI_Icons/inventory/gloves.png b/icons/UI_Icons/inventory/gloves.png new file mode 100644 index 0000000000..2c8a16cbdb Binary files /dev/null and b/icons/UI_Icons/inventory/gloves.png differ diff --git a/icons/UI_Icons/inventory/hand_l.png b/icons/UI_Icons/inventory/hand_l.png new file mode 100644 index 0000000000..b09228d65f Binary files /dev/null and b/icons/UI_Icons/inventory/hand_l.png differ diff --git a/icons/UI_Icons/inventory/hand_r.png b/icons/UI_Icons/inventory/hand_r.png new file mode 100644 index 0000000000..0e05a487e0 Binary files /dev/null and b/icons/UI_Icons/inventory/hand_r.png differ diff --git a/icons/UI_Icons/inventory/head.png b/icons/UI_Icons/inventory/head.png new file mode 100644 index 0000000000..11e2d2254c Binary files /dev/null and b/icons/UI_Icons/inventory/head.png differ diff --git a/icons/UI_Icons/inventory/id.png b/icons/UI_Icons/inventory/id.png new file mode 100644 index 0000000000..4469591d36 Binary files /dev/null and b/icons/UI_Icons/inventory/id.png differ diff --git a/icons/UI_Icons/inventory/mask.png b/icons/UI_Icons/inventory/mask.png new file mode 100644 index 0000000000..82e5108937 Binary files /dev/null and b/icons/UI_Icons/inventory/mask.png differ diff --git a/icons/UI_Icons/inventory/neck.png b/icons/UI_Icons/inventory/neck.png new file mode 100644 index 0000000000..78ad3ce3b1 Binary files /dev/null and b/icons/UI_Icons/inventory/neck.png differ diff --git a/icons/UI_Icons/inventory/pocket.png b/icons/UI_Icons/inventory/pocket.png new file mode 100644 index 0000000000..f42399dca0 Binary files /dev/null and b/icons/UI_Icons/inventory/pocket.png differ diff --git a/icons/UI_Icons/inventory/shoes.png b/icons/UI_Icons/inventory/shoes.png new file mode 100644 index 0000000000..d20f7ef4d1 Binary files /dev/null and b/icons/UI_Icons/inventory/shoes.png differ diff --git a/icons/UI_Icons/inventory/suit.png b/icons/UI_Icons/inventory/suit.png new file mode 100644 index 0000000000..e9c48e8069 Binary files /dev/null and b/icons/UI_Icons/inventory/suit.png differ diff --git a/icons/UI_Icons/inventory/suit_storage.png b/icons/UI_Icons/inventory/suit_storage.png new file mode 100644 index 0000000000..9722eb1029 Binary files /dev/null and b/icons/UI_Icons/inventory/suit_storage.png differ diff --git a/icons/UI_Icons/inventory/uniform.png b/icons/UI_Icons/inventory/uniform.png new file mode 100644 index 0000000000..292b3324b5 Binary files /dev/null and b/icons/UI_Icons/inventory/uniform.png differ 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');