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 +="
| [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 |
"
- 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 = "
"
- 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"/>