TGUI Stripping (#22131)

* strippable

* readds whitespace

* corrects my Initialize placement

* fix typo

* change icons to 64px/32px

* makes it look good
This commit is contained in:
ynot01
2024-06-05 21:00:45 -04:00
committed by GitHub
parent 04580456d2
commit 62d721050d
81 changed files with 1581 additions and 486 deletions

View File

@@ -310,7 +310,7 @@
#define WIZARD_AGE_MIN 30 //youngest a wizard can be
#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 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

View File

@@ -55,14 +55,14 @@
#define LAVAPROTECT (1<<0)
/// SUIT and HEAD items which stop pressure damage.
/// To stop you taking all pressure damage you must have both a suit and head item with these flags. First one is high pressure (fires), second one is low (space).
#define STOPSHIGHPRESSURE (1<<1)
#define STOPSHIGHPRESSURE (1<<1)
#define STOPSLOWPRESSURE (1<<2)
/// Blocks the effect that chemical clouds would have on a mob --glasses, mask and helmets ONLY!
#define BLOCK_GAS_SMOKE_EFFECT (1<<3)
/// Mask allows internals
#define MASKINTERNALS (1<<4)
/// Prevents from slipping on wet floors, in space etc
#define NOSLIP (1<<5)
#define NOSLIP (1<<5)
/// Prevents from slipping on frozen floors
#define NOSLIP_ICE (1<<6)
/// Prevents syringes, parapens and hypos if the external suit or helmet (if targeting head) has this flag.
@@ -71,7 +71,7 @@
/// The voicebox in this clothing can be toggled.
#define VOICEBOX_TOGGLABLE (1<<8)
/// The voicebox is currently turned off.
#define VOICEBOX_DISABLED (1<<9)
#define VOICEBOX_DISABLED (1<<9)
/// Prevents you from feeling sad if you shower in them
#define SHOWEROKAY (1<<10)
/// Allows helmets and glasses to scan reagents.
@@ -82,6 +82,8 @@
#define HEADINTERNALS (1<<13)
/// Clothes that use large icons, for applying the proper overlays like blood
#define LARGE_WORN_ICON (1<<14)
/// Clothes that cause a larger notification when placed on a person.
#define DANGEROUS_OBJECT (1<<15)
#define STOPSPRESSUREDAMAGE (STOPSHIGHPRESSURE | STOPSLOWPRESSURE) //covers both high and low pressure

View File

@@ -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

View File

@@ -41,7 +41,7 @@
*/
///Initialize the lazylist
#define LAZYINITLIST(L) if (!L) L = list()
#define LAZYINITLIST(L) if (!L) { L = list(); }
///If the provided list is empty, set it to null
#define UNSETEMPTY(L) if (L && !length(L)) L = null
///Remove an item from the list, set the list to null if empty
@@ -76,6 +76,11 @@
///Returns the list if it's actually a valid list, otherwise will initialize it
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
/// 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;
///Adds to the item K the value V, if the list is null it will initialize it
#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V;
///Removes the value V from the item K, if the item K is empty will remove it from the list, if the list is empty will set the list to null

View File

@@ -0,0 +1,491 @@
/// 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
argument_hash_start_idx = 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_REF(mouse_drop_onto))
src.items = items
src.should_strip_proc_path = should_strip_proc_path
/datum/element/strippable/Detach(datum/source, force)
. = ..()
UnregisterSignal(source, COMSIG_MOUSEDROP_ONTO)
if (!isnull(strip_menus))
QDEL_NULL(strip_menus[source])
/datum/element/strippable/proc/mouse_drop_onto(datum/source, atom/over, mob/user)
SIGNAL_HANDLER
if (user == source)
return
if (over != 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.combat_mode)
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, TYPE_PROC_REF(/datum,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 class='warning'>You can't put [equipping] on [source], it's stuck to your hand!</span>")
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))
var/obj/item/clothing/clothing = source
if(clothing.clothing_flags & DANGEROUS_OBJECT)
source.visible_message(
"<span class='danger'>[user] tries to put [equipping] on [source].</span>",
"<span class='userdanger'>[user] tries to put [equipping] on you.</span>",
ignored_mobs = user,
)
else
source.visible_message(
"<span class='notice'>[user] tries to put [equipping] on [source].</span>",
"<span class='notice'>[user] tries to put [equipping] on you.</span>",
ignored_mobs = user,
)
to_chat(user, "<span class='notice'>You try to put [equipping] on [source]...</span>")
var/log = "[key_name(source)] is having [equipping] put on them by [key_name(user)]"
source.log_message(log, LOG_ATTACK, color="red")
user.log_message(log, LOG_ATTACK, 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)
/// 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
if (HAS_TRAIT(item, TRAIT_NO_STRIP))
return FALSE
source.visible_message(
"<span class='warning'>[user] tries to remove [source]'s [item].</span>",
"<span class='userdanger'>[user] tries to remove your [item].</span>",
ignored_mobs = user,
)
to_chat(user, "<span class='danger'>You try to remove [source]'s [item]...</span>")
source.log_message("[key_name(source)] is being stripped of [item] by [key_name(src)]", LOG_ATTACK, color="red")
user.log_message("[key_name(source)] is being stripped of [item] by [key_name(src)]", LOG_ATTACK, color="red", log_globally=FALSE)
item.add_fingerprint(src)
return TRUE
/// The proc that unequips the item from the source. This should not yield.
/datum/strippable_item/proc/finish_unequip(atom/source, mob/user)
/// Returns a STRIPPABLE_OBSCURING_* define to report on whether or not this is obscured.
/datum/strippable_item/proc/get_obscuring(atom/source)
SHOULD_NOT_SLEEP(TRUE)
return STRIPPABLE_OBSCURING_NONE
/// Returns the ID of this item's strippable action.
/// Return `null` if there is no alternate action.
/// Any return value of this must be in StripMenu.
/datum/strippable_item/proc/get_alternate_action(atom/source, mob/user)
return null
/// Performs an alternative action on this strippable_item.
/// `has_alternate_action` needs to be TRUE.
/datum/strippable_item/proc/alternate_action(atom/source, mob/user)
/// Returns whether or not this item should show.
/datum/strippable_item/proc/should_show(atom/source, mob/user)
return TRUE
/// A preset for equipping items onto mob slots
/datum/strippable_item/mob_item_slot
/// The ITEM_SLOT_* to equip to.
var/item_slot
/datum/strippable_item/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 class='warning'>\The [equipping] doesn't fit in that place!</span>")
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_after(user, get_equip_delay(equipping), source))
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 (!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 (carbon_source.check_obscured_slots() & item_slot) \
? 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_after(user, strip_delay || item.strip_delay, source, interaction_key = REF(item)))
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
source.log_message("[key_name(source)] has been stripped of [item] by [key_name(user)]", LOG_ATTACK, color="red")
user.log_message("[key_name(source)] has been stripped of [item] by [key_name(user)]", LOG_ATTACK, color="red", log_globally=FALSE)
// Updates speed in case stripped speed affecting item
source.update_movespeed()
/// 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) || (HAS_TRAIT(item, TRAIT_NO_STRIP)))
items[strippable_key] = result
continue
LAZYINITLIST(result)
result["icon"] = icon2base64(icon(item.icon, item.icon_state))
result["name"] = item.name
result["alternate"] = item_data.get_alternate_action(owner, user)
items[strippable_key] = result
data["items"] = items
// While most `\the`s are implicit, this one is not.
// In this case, `\The` would otherwise be used.
// This doesn't match with what it's used for, which is to say "Stripping the alien drone",
// as opposed to "Stripping The alien drone".
// Human names will still show without "the", as they are proper nouns.
data["name"] = "\the [owner]"
return data
/datum/strip_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return
. = TRUE
var/mob/user = 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_status(mob/user, datum/ui_state/state)
. = ..()
if (isliving(user))
var/mob/living/living_user = user
if (
. == UI_UPDATE \
&& user.stat == CONSCIOUS \
&& living_user.body_position == LYING_DOWN \
&& user.Adjacent(owner)
)
return UI_INTERACTIVE
/// Creates an assoc list of keys to /datum/strippable_item
/proc/create_strippable_list(types)
var/list/strippable_items = list()
for (var/strippable_type in types)
var/datum/strippable_item/strippable_item = new strippable_type
strippable_items[strippable_item.key] = strippable_item
return strippable_items

View File

@@ -1038,7 +1038,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
/obj/item/proc/canStrip(mob/stripper, mob/owner)
SHOULD_BE_PURE(TRUE)
return !HAS_TRAIT(src, TRAIT_NODROP)
return !HAS_TRAIT(src, TRAIT_NODROP) && !(item_flags & ABSTRACT)
/obj/item/proc/doStrip(mob/stripper, mob/owner)
return owner.dropItemToGround(src)

View File

@@ -53,7 +53,7 @@
user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30)
addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, 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(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(equipper && !is_servant_of_ratvar(equipper))
return 0
return ..()
@@ -97,7 +97,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(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(equipper && !is_servant_of_ratvar(equipper))
return 0
return ..()
@@ -160,7 +160,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(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(equipper && !is_servant_of_ratvar(equipper))
return 0
return ..()
@@ -210,7 +210,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(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(equipper && !is_servant_of_ratvar(equipper))
return 0
return ..()

View File

@@ -427,7 +427,7 @@
icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])"
else
icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])"
stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]")
continue
#endif
@@ -481,7 +481,7 @@
I.Blend(c, ICON_MULTIPLY)
var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
if(!sprites[imgid])
Insert(imgid, I)
@@ -720,3 +720,24 @@
//glass door no overlay
icon = icon(airlocks[airlock_name] , "closed" , SOUTH)
Insert(sanitize_css_class_name("[airlock_name]Glass"), icon)
/datum/asset/simple/inventory
assets = list(
"inventory-glasses.png" = 'icons/UI_Icons/inventory_midnight/glasses.png',
"inventory-head.png" = 'icons/UI_Icons/inventory_midnight/head.png',
"inventory-neck.png" = 'icons/UI_Icons/inventory_midnight/neck.png',
"inventory-mask.png" = 'icons/UI_Icons/inventory_midnight/mask.png',
"inventory-ears.png" = 'icons/UI_Icons/inventory_midnight/ears.png',
"inventory-uniform.png" = 'icons/UI_Icons/inventory_midnight/uniform.png',
"inventory-suit.png" = 'icons/UI_Icons/inventory_midnight/suit.png',
"inventory-gloves.png" = 'icons/UI_Icons/inventory_midnight/gloves.png',
"inventory-hand_l.png" = 'icons/UI_Icons/inventory_midnight/hand_l.png',
"inventory-hand_r.png" = 'icons/UI_Icons/inventory_midnight/hand_r.png',
"inventory-shoes.png" = 'icons/UI_Icons/inventory_midnight/shoes.png',
"inventory-suit_storage.png" = 'icons/UI_Icons/inventory_midnight/suit_storage.png',
"inventory-id.png" = 'icons/UI_Icons/inventory_midnight/id.png',
"inventory-belt.png" = 'icons/UI_Icons/inventory_midnight/belt.png',
"inventory-back.png" = 'icons/UI_Icons/inventory_midnight/back.png',
"inventory-pocket.png" = 'icons/UI_Icons/inventory_midnight/pocket.png',
"inventory-collar.png" = 'icons/UI_Icons/inventory_midnight/collar.png',
)

View File

@@ -183,7 +183,7 @@
.= ..()
AddComponent(/datum/component/squeak, list('sound/effects/collarbell1.ogg'=1,'sound/effects/collarbell2.ogg'=1), 50, 100, 2)
/obj/item/clothing/neck/petcollar/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0)
/obj/item/clothing/neck/petcollar/mob_can_equip(mob/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(ishuman(M))
return FALSE
return ..()

View File

@@ -743,7 +743,7 @@
armor = list(MELEE = 30, BULLET = 5, LASER = 10, ENERGY = 5, BOMB = 10, BIO = 100, RAD = 75, FIRE = 60, ACID = 30, ELECTRIC = 100)
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/clown
/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, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(!..() || !ishuman(M))
return FALSE
var/mob/living/carbon/human/H = M

View File

@@ -330,7 +330,7 @@
if(HAS_TRAIT(I, TRAIT_NODROP) && !force)
return FALSE
if((SEND_SIGNAL(I, COMSIG_ITEM_PRE_UNEQUIP, force, newloc, no_move, invdrop, silent) & COMPONENT_ITEM_BLOCK_UNEQUIP) && !force)
return FALSE
@@ -407,14 +407,13 @@
/mob/living/carbon/proc/check_obscured_slots(transparent_protection)
var/list/obscured = list()
var/obscured = NONE
var/hidden_slots = NONE
for(var/obj/item/I in get_equipped_items())
hidden_slots |= I.flags_inv
hidden_slots |= I.flags_prot
for(var/obj/item/equipped_item in get_equipped_items())
hidden_slots |= equipped_item.flags_inv
if(transparent_protection)
hidden_slots |= I.transparent_protection
hidden_slots |= equipped_item.transparent_protection
if(hidden_slots & HIDENECK)
obscured |= ITEM_SLOT_NECK
@@ -462,7 +461,7 @@
to_chat(M, span_warning("You are unable to equip that!"))
return FALSE
/mob/verb/quick_equip()
set name = "quick-equip"
set hidden = TRUE

View File

@@ -5,8 +5,6 @@
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 5, /obj/item/stack/sheet/animalhide/xeno = 1)
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
@@ -18,49 +16,16 @@
bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien,
/obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien)
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,
)))
/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 += {"
<HTML><HEAD><meta charset='UTF-8'></HEAD><BODY>
<HR>
<span class='big bold'>[name]</span>
<HR>"}
for(var/i in 1 to held_items.len)
var/obj/item/I = get_item_for_held_index(i)
dat += "<BR><B>[get_held_index_name(i)]:</B><A href='?src=[REF(src)];item=[ITEM_SLOT_HANDS];hand_index=[i]'>[(I && !(I.item_flags & ABSTRACT)) ? I : "<font color=grey>Empty</font>"]</a>"
dat += "<BR><A href='?src=[REF(src)];pouches=1'>Empty Pouches</A>"
if(handcuffed)
dat += "<BR><A href='?src=[REF(src)];item=[ITEM_SLOT_HANDCUFFED]'>Handcuffed</A>"
if(legcuffed)
dat += "<BR><A href='?src=[REF(src)];item=[ITEM_SLOT_LEGCUFFED]'>Legcuffed</A>"
dat += {"
<BR>
<BR><A href='?src=[REF(user)];mach_close=mob[REF(src)]'>Close</A>
</BODY></HTML>
"}
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
if(href_list["pouches"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
visible_message(span_danger("[usr] tries to empty [src]'s pouches."), \
span_userdanger("[usr] tries to empty [src]'s pouches."))
if(do_after(usr, POCKET_STRIP_DELAY * 0.5, src))
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
..(I, cuff_break = INSTANT_CUFFBREAK)

View File

@@ -53,9 +53,6 @@
// 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

View File

@@ -214,68 +214,8 @@
return 0
/mob/living/carbon/show_inv(mob/user)
user.set_machine(src)
var/dat = {"
<HTML><HEAD><meta charset='UTF-8'></HEAD><BODY>
<HR>
<B><FONT size=3>[name]</FONT></B>
<HR>
<BR><B>Head:</B> <A href='?src=[REF(src)];item=[ITEM_SLOT_HEAD]'>[(head && !(head.item_flags & ABSTRACT)) ? head : "Nothing"]</A>"}
var/list/obscured = check_obscured_slots()
if(ITEM_SLOT_NECK in obscured)
dat += "<BR><B>Neck:</B> Obscured"
else
dat += "<BR><B>Neck:</B> <A href='?src=[REF(src)];item=[ITEM_SLOT_NECK]'>[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? (wear_neck) : "Nothing"]</A>"
if(ITEM_SLOT_MASK in obscured)
dat += "<BR><B>Mask:</B> Obscured"
else
dat += "<BR><B>Mask:</B> <A href='?src=[REF(src)];item=[ITEM_SLOT_MASK]'>[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Nothing"]</a>"
for(var/i in 1 to held_items.len)
var/obj/item/I = get_item_for_held_index(i)
dat += "<BR><B>[get_held_index_name(i)]:</B> </td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_HANDS];hand_index=[i]'>[(I && !(I.item_flags & ABSTRACT)) ? I : "Nothing"]</a>"
dat += "<BR><B>Back:</B> <A href='?src=[REF(src)];item=[ITEM_SLOT_BACK]'>[back ? back : "Nothing"]</A>"
if(istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank))
dat += "<BR><A href='?src=[REF(src)];internal=1'>[internal ? "Disable Internals" : "Set Internals"]</A>"
if(handcuffed)
dat += "<BR><A href='?src=[REF(src)];item=[ITEM_SLOT_HANDCUFFED]'>Handcuffed</A>"
if(legcuffed)
dat += "<BR><A href='?src=[REF(src)];item=[ITEM_SLOT_LEGCUFFED]'>Legcuffed</A>"
dat += {"
<BR>
<BR><A href='?src=[REF(user)];mach_close=mob[REF(src)]'>Close</A>
</BODY></HTML>
"}
user << browse(dat, "window=mob[REF(src)];size=325x500")
onclose(user, "mob[REF(src)]")
/mob/living/carbon/Topic(href, href_list)
..()
//strip panel
if(href_list["internal"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
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 & MASKINTERNALS))
visible_message(span_danger("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name]."), \
span_userdanger("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name]."))
if(do_after(usr, POCKET_STRIP_DELAY, src, interaction_key = REF(ITEM)))
if(internal)
cutoff_internals()
else if(ITEM && istype(ITEM, /obj/item/tank))
if((wear_mask && (wear_mask.clothing_flags & MASKINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE))
open_internals(ITEM)
visible_message(span_danger("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name]."), \
span_userdanger("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name]."))
// Embed Stuff
if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts

View File

@@ -0,0 +1,132 @@
/datum/strippable_item/mob_item_slot/head
key = STRIPPABLE_ITEM_HEAD
item_slot = ITEM_SLOT_HEAD
/datum/strippable_item/mob_item_slot/back
key = STRIPPABLE_ITEM_BACK
item_slot = ITEM_SLOT_BACK
/datum/strippable_item/mob_item_slot/back/get_alternate_action(atom/source, mob/user)
return get_strippable_alternate_action_internals(get_item(source), source)
/datum/strippable_item/mob_item_slot/mask
key = STRIPPABLE_ITEM_MASK
item_slot = ITEM_SLOT_MASK
/datum/strippable_item/mob_item_slot/neck
key = STRIPPABLE_ITEM_NECK
item_slot = ITEM_SLOT_NECK
/datum/strippable_item/mob_item_slot/handcuffs
key = STRIPPABLE_ITEM_HANDCUFFS
item_slot = ITEM_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 = ITEM_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, "<span class='warning'>\The [equipping] doesn't fit in that place!</span>")
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_after(user, equipping.equip_delay_other, source))
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 (!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

View File

@@ -36,6 +36,7 @@
AddComponent(/datum/component/personal_crafting)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HUMAN, 1, -6)
AddComponent(/datum/component/bloodysoles/feet, FOOTPRINT_SPRITE_SHOES)
AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human,should_strip))
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
@@ -153,115 +154,6 @@
. += "* [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 += "<table>"
for(var/i in 1 to held_items.len)
var/obj/item/I = get_item_for_held_index(i)
dat += "<tr><td><B>[get_held_index_name(i)]:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_HANDS];hand_index=[i]'>[(I && !(I.item_flags & ABSTRACT)) ? I : "<font color=grey>Empty</font>"]</a></td></tr>"
dat += "<tr><td>&nbsp;</td></tr>"
dat += "<tr><td><B>Back:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_BACK]'>[(back && !(back.item_flags & ABSTRACT)) ? back : "<font color=grey>Empty</font>"]</A>"
if(has_breathable_mask && istype(back, /obj/item/tank))
dat += "&nbsp;<A href='?src=[REF(src)];internal=[ITEM_SLOT_BACK]'>[internal ? "Disable Internals" : "Set Internals"]</A>"
dat += "</td></tr><tr><td>&nbsp;</td></tr>"
dat += "<tr><td><B>Head:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_HEAD]'>[(head && !(head.item_flags & ABSTRACT)) ? head : "<font color=grey>Empty</font>"]</A></td></tr>"
if(ITEM_SLOT_MASK in obscured)
dat += "<tr><td><font color=grey><B>Mask:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Mask:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_MASK]'>[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "<font color=grey>Empty</font>"]</A></td></tr>"
if(ITEM_SLOT_NECK in obscured)
dat += "<tr><td><font color=grey><B>Neck:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Neck:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_NECK]'>[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "<font color=grey>Empty</font>"]</A></td></tr>"
if(ITEM_SLOT_EYES in obscured)
dat += "<tr><td><font color=grey><B>Eyes:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Eyes:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_EYES]'>[(glasses && !(glasses.item_flags & ABSTRACT)) ? glasses : "<font color=grey>Empty</font>"]</A></td></tr>"
if(ITEM_SLOT_EARS in obscured)
dat += "<tr><td><font color=grey><B>Ears:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Ears:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_EARS]'>[(ears && !(ears.item_flags & ABSTRACT)) ? ears : "<font color=grey>Empty</font>"]</A></td></tr>"
dat += "<tr><td>&nbsp;</td></tr>"
dat += "<tr><td><B>Exosuit:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_OCLOTHING]'>[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "<font color=grey>Empty</font>"]</A></td></tr>"
if(wear_suit)
if(ITEM_SLOT_SUITSTORE in obscured)
dat += "<tr><td><font color=grey>&nbsp;&#8627;<B>Suit Storage:</B></font></td></tr>"
else
dat += "<tr><td>&nbsp;&#8627;<B>Suit Storage:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_SUITSTORE]'>[(s_store && !(s_store.item_flags & ABSTRACT)) ? s_store : "<font color=grey>Empty</font>"]</A>"
if(has_breathable_mask && istype(s_store, /obj/item/tank))
dat += "&nbsp;<A href='?src=[REF(src)];internal=[ITEM_SLOT_SUITSTORE]'>[internal ? "Disable Internals" : "Set Internals"]</A>"
dat += "</td></tr>"
else
dat += "<tr><td><font color=grey>&nbsp;&#8627;<B>Suit Storage:</B></font></td></tr>"
if(ITEM_SLOT_FEET in obscured)
dat += "<tr><td><font color=grey><B>Shoes:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Shoes:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_FEET]'>[(shoes && !(shoes.item_flags & ABSTRACT)) ? shoes : "<font color=grey>Empty</font>"]</A></td></tr>"
if(ITEM_SLOT_GLOVES in obscured)
dat += "<tr><td><font color=grey><B>Gloves:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Gloves:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_GLOVES]'>[(gloves && !(gloves.item_flags & ABSTRACT)) ? gloves : "<font color=grey>Empty</font>"]</A></td></tr>"
if(ITEM_SLOT_ICLOTHING in obscured)
dat += "<tr><td><font color=grey><B>Uniform:</B></font></td><td><font color=grey>Obscured</font></td></tr>"
else
dat += "<tr><td><B>Uniform:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_ICLOTHING]'>[(w_uniform && !(w_uniform.item_flags & ABSTRACT)) ? w_uniform : "<font color=grey>Empty</font>"]</A></td></tr>"
// Yogs start - taking an axe at this stupid handmade-HTML garbo code
var/obj/item/bodypart/chest = get_bodypart(BODY_ZONE_CHEST) // This is a lil verbose but the lint demands it
var/is_ipc_or_something = (chest?.status == BODYPART_ROBOTIC)
var/can_be_nakey = is_ipc_or_something || (dna && dna.species.nojumpsuit)
if((ITEM_SLOT_ICLOTHING in obscured) || (w_uniform == null && !can_be_nakey))
dat += "<tr><td><font color=grey>&nbsp;&#8627;<B>Pockets:</B></font></td></tr>"
dat += "<tr><td><font color=grey>&nbsp;&#8627;<B>ID:</B></font></td></tr>"
dat += "<tr><td><font color=grey>&nbsp;&#8627;<B>Belt:</B></font></td></tr>"
else
var/funny_arrow_character = ""
if(!can_be_nakey) // The funny arrow only really makes sense if this creature *needs* the uniform in order to hold these things.
// Less so when we're an IPC or something.
funny_arrow_character = "&nbsp;&#8627;"
dat += "<tr><td>[funny_arrow_character]<B>Belt:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_BELT]'>[(belt && !(belt.item_flags & ABSTRACT)) ? belt : "<font color=grey>Empty</font>"]</A>"
if(has_breathable_mask && istype(belt, /obj/item/tank))
dat += "&nbsp;<A href='?src=[REF(src)];internal=[ITEM_SLOT_BELT]'>[internal ? "Disable Internals" : "Set Internals"]</A>"
dat += "</td></tr>"
dat += "<tr><td>[funny_arrow_character]<B>Pockets:</B></td><td><A href='?src=[REF(src)];pockets=left'>[(l_store && !(l_store.item_flags & ABSTRACT)) ? "Left (Full)" : "<font color=grey>Left (Empty)</font>"]</A>"
dat += "&nbsp;<A href='?src=[REF(src)];pockets=right'>[(r_store && !(r_store.item_flags & ABSTRACT)) ? "Right (Full)" : "<font color=grey>Right (Empty)</font>"]</A></td></tr>"
dat += "<tr><td>[funny_arrow_character]<B>ID:</B></td><td><A href='?src=[REF(src)];item=[ITEM_SLOT_ID]'>[(wear_id && !(wear_id.item_flags & ABSTRACT)) ? wear_id : "<font color=grey>Empty</font>"]</A></td></tr>"
// Yogs end - garbo code axe
// yogs start - show bandaged parts
for (var/obj/item/bodypart/org in bodyparts)
if (org.bandaged)
dat += "<tr><td><i>[org.name]</i> wrapped with:</td><td><a href='byond://?src=\ref[src];unwrap=\ref[org.bandaged]'>[org.bandaged]</a></td></tr>"
// yogs end
if(handcuffed)
dat += "<tr><td><B>Handcuffed:</B> <A href='?src=[REF(src)];item=[ITEM_SLOT_HANDCUFFED]'>Remove</A></td></tr>"
if(legcuffed)
dat += "<tr><td><A href='?src=[REF(src)];item=[ITEM_SLOT_LEGCUFFED]'>Legcuffed</A></td></tr>"
dat += {"</table>
<A href='?src=[REF(user)];mach_close=mob[REF(src)]'>Close</A>
"}
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/proc/on_entered(datum/source, atom/movable/AM, ...)
@@ -307,40 +199,6 @@
to_chat(usr, span_warning("You can't reach that! Something is covering it."))
return
if(href_list["pockets"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) //TODO: Make it match (or intergrate it into) strippanel so you get 'item cannot fit here' warnings if mob_can_equip fails
var/pocket_side = href_list["pockets"]
var/pocket_id = (pocket_side == "right" ? ITEM_SLOT_RPOCKET : ITEM_SLOT_LPOCKET)
var/obj/item/pocket_item = (pocket_id == ITEM_SLOT_RPOCKET ? 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, span_warning("You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!"))
to_chat(usr, span_notice("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, span_notice("You try to place [place_item] into [src]'s [pocket_side] pocket."))
delay_denominator = 4
else
return
if(do_after(usr, POCKET_STRIP_DELAY/delay_denominator, src, interaction_key = REF(pocket_item))) //placing an item into the pocket is 4 times faster
if(pocket_item)
if(pocket_item == (pocket_id == ITEM_SLOT_RPOCKET ? r_store : l_store)) //item still in the pocket we search
dropItemToGround(pocket_item)
if(usr.IsAdvancedToolUser())
usr.put_in_hands(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)
//do nothing otherwise
//updating inv screen after handled by living/Topic()
else
// Display a warning if the user mocks up
to_chat(src, span_warning("You feel your [pocket_side] pocket being fumbled with!"))
///////HUDs///////
if(href_list["hud"])
if(ishuman(usr))

View File

@@ -0,0 +1,215 @@
#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 = ITEM_SLOT_EYES
/datum/strippable_item/mob_item_slot/ears
key = STRIPPABLE_ITEM_EARS
item_slot = ITEM_SLOT_EARS
/datum/strippable_item/mob_item_slot/jumpsuit
key = STRIPPABLE_ITEM_JUMPSUIT
item_slot = ITEM_SLOT_ICLOTHING
/datum/strippable_item/mob_item_slot/jumpsuit/get_alternate_action(atom/source, mob/user)
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)
var/obj/item/clothing/under/jumpsuit = get_item(source)
if (!istype(jumpsuit))
return null
to_chat(source, "<span class='notice'>[user] is trying to adjust your [jumpsuit.name].")
if (!do_after(user, jumpsuit.strip_delay * 0.5, source))
return
to_chat(source, "<span class='notice'>[user] successfully adjusted your [jumpsuit.name].")
jumpsuit.toggle_jumpsuit_adjust()
if (!ismob(source))
return
var/mob/mob_source = source
mob_source.update_inv_w_uniform()
mob_source.update_body()
/datum/strippable_item/mob_item_slot/suit
key = STRIPPABLE_ITEM_SUIT
item_slot = ITEM_SLOT_OCLOTHING
/datum/strippable_item/mob_item_slot/gloves
key = STRIPPABLE_ITEM_GLOVES
item_slot = ITEM_SLOT_GLOVES
/datum/strippable_item/mob_item_slot/feet
key = STRIPPABLE_ITEM_FEET
item_slot = ITEM_SLOT_FEET
/datum/strippable_item/mob_item_slot/feet/get_alternate_action(atom/source, mob/user)
return null
/datum/strippable_item/mob_item_slot/feet/alternate_action(atom/source, mob/user)
return
/datum/strippable_item/mob_item_slot/suit_storage
key = STRIPPABLE_ITEM_SUIT_STORAGE
item_slot = ITEM_SLOT_SUITSTORE
/datum/strippable_item/mob_item_slot/suit_storage/get_alternate_action(atom/source, mob/user)
return get_strippable_alternate_action_internals(get_item(source), source)
/datum/strippable_item/mob_item_slot/suit_storage/alternate_action(atom/source, mob/user)
return strippable_alternate_action_internals(get_item(source), source, user)
/datum/strippable_item/mob_item_slot/id
key = STRIPPABLE_ITEM_ID
item_slot = ITEM_SLOT_ID
/datum/strippable_item/mob_item_slot/belt
key = STRIPPABLE_ITEM_BELT
item_slot = ITEM_SLOT_BELT
/datum/strippable_item/mob_item_slot/belt/get_alternate_action(atom/source, mob/user)
return get_strippable_alternate_action_internals(get_item(source), source)
/datum/strippable_item/mob_item_slot/belt/alternate_action(atom/source, mob/user)
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 class='notice'>You try to empty [source]'s [pocket_side] pocket.</span>")
var/log_message = "[key_name(source)] is being pickpocketed of [item] by [key_name(user)] ([pocket_side])"
source.log_message(log_message, LOG_ATTACK, color="red")
user.log_message(log_message, LOG_ATTACK, color="red", log_globally=FALSE)
item.add_fingerprint(src)
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 class='warning'>You feel your [pocket_side] pocket being fumbled with!</span>")
/datum/strippable_item/mob_item_slot/pocket/left
key = STRIPPABLE_ITEM_LPOCKET
item_slot = ITEM_SLOT_LPOCKET
pocket_side = "left"
/datum/strippable_item/mob_item_slot/pocket/right
key = STRIPPABLE_ITEM_RPOCKET
item_slot = ITEM_SLOT_RPOCKET
pocket_side = "right"
/proc/get_strippable_alternate_action_internals(obj/item/item, atom/source)
if (!iscarbon(source))
return
var/mob/living/carbon/carbon_source = source
var/obj/item/clothing/mask = carbon_source.wear_mask
if (!istype(mask))
return
if ((mask.clothing_flags & MASKINTERNALS) && 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
var/mob/living/carbon/carbon_source = source
if (!istype(carbon_source))
return
var/obj/item/clothing/mask = carbon_source.wear_mask
if (!istype(mask) || !(mask.clothing_flags & MASKINTERNALS))
return
carbon_source.visible_message(
"<span class='danger'>[user] tries to [isnull(carbon_source.internal) ? "open": "close"] the valve on [source]'s [item.name].</span>",
"<span class='userdanger'>[user] tries to [isnull(carbon_source.internal) ? "open": "close"] the valve on your [item.name].</span>",
ignored_mobs = user,
)
to_chat(user, "<span class='notice'>You try to [isnull(carbon_source.internal) ? "open": "close"] the valve on [source]'s [item.name]...</span>")
if(!do_after(user, INTERNALS_TOGGLE_DELAY, carbon_source))
return
if(carbon_source.internal)
carbon_source.close_internals()
// This isn't meant to be FALSE, it correlates to the icon's name.
else if (!QDELETED(item))
if(!carbon_source.try_open_internals(item))
return
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 class='notice'>You [isnull(carbon_source.internal) ? "close" : "open"] the valve on [source]'s [item.name].</span>")
#undef INTERNALS_TOGGLE_DELAY
#undef POCKET_EQUIP_DELAY

View File

@@ -885,11 +885,6 @@
if(what.doStrip(src, who))
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)]")
// 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)
@@ -921,11 +916,6 @@
else
who.equip_to_slot(what, where, TRUE)
if(Adjacent(who)) //update inventory window
who.show_inv(src)
else
src << browse(null,"window=mob[REF(who)]")
/mob/living/singularity_pull(S, current_size)
..()
if(current_size >= STAGE_SIX) //your puny magboots/wings/whatever will not save you against supermatter singularity
@@ -1207,7 +1197,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
var/datum/status_effect/fire_handler/fire_handler = has_status_effect(/datum/status_effect/fire_handler)
if(fire_handler)
fire_handler.update_overlay()
/**
* Extinguish all fire on the mob
*
@@ -1700,7 +1690,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
// if(!resting)
// get_up()
set_resting(FALSE)
/mob/living/proc/move_to_error_room()
var/obj/effect/landmark/error/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list
if(error_landmark)

View File

@@ -133,6 +133,7 @@
/mob/living/simple_animal/pet/dog/corgi/Initialize(mapload)
. = ..()
regenerate_icons()
AddElement(/datum/element/strippable, GLOB.strippable_corgi_items)
/mob/living/simple_animal/pet/dog/corgi/exoticcorgi/Initialize(mapload)
. = ..()
@@ -143,19 +144,164 @@
..(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)
return corgi_source.inventory_head
var/dat = "<HTML><HEAD><meta charset='UTF-8'></HEAD><BODY><div align='center'><b>Inventory of [name]</b></div><p>"
dat += "<br><B>Head:</B> <A href='?src=[REF(src)];[inventory_head ? "remove_inv=head'>[inventory_head]" : "add_inv=head'>Nothing"]</A>"
dat += "<br><B>Back:</B> <A href='?src=[REF(src)];[inventory_back ? "remove_inv=back'>[inventory_back]" : "add_inv=back'>Nothing"]</A>"
dat += "<br><B>Collar:</B> <A href='?src=[REF(src)];[pcollar ? "remove_inv=collar'>[pcollar]" : "add_inv=collar'>Nothing"]</A></BODY></HTML>"
/datum/strippable_item/corgi_head/finish_equip(atom/source, obj/item/equipping, mob/user)
var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source
if (!istype(corgi_source))
return
user << browse(dat, "window=mob[REF(src)];size=325x500")
onclose(user, "mob[REF(src)]")
corgi_source.place_on_head(equipping, user)
/datum/strippable_item/corgi_head/finish_unequip(atom/source, obj/item/equipping, mob/user)
var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source
if (!istype(corgi_source))
return
user.put_in_hands(corgi_source.inventory_head)
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, "<span class='warning'>You set [equipping] on [source]'s back, but it falls off!</span>")
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)
var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source
if (!istype(corgi_source))
return
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
user.put_in_hands(corgi_source.inventory_back)
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, "<span class='warning'>That's not a collar.</span>")
return FALSE
return TRUE
/datum/strippable_item/corgi_collar/finish_equip(atom/source, obj/item/equipping, mob/user)
var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source
if (!istype(corgi_source))
return
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
user.put_in_hands(corgi_source.pcollar)
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, "<span class='warning'>You can't pin [equipping] to [source]!</span>")
return FALSE
return TRUE
/datum/strippable_item/corgi_id/finish_equip(atom/source, obj/item/equipping, mob/user)
var/mob/living/simple_animal/pet/dog/corgi/corgi_source = source
if (!istype(corgi_source))
return
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
user.put_in_hands(corgi_source.access_card)
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
@@ -198,102 +344,6 @@
..()
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, span_danger("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, span_danger("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,span_warning("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, span_warning("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].",span_notice("You rest your hand on [src]'s back for a moment."))
return
if(!usr.temporarilyRemoveItemFromInventory(item_to_add))
to_chat(usr, span_warning("\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, span_warning("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(0.1 SECONDS)
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.
@@ -301,10 +351,6 @@
/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, TYPE_PROC_REF(/obj/item, afterattack), src, user, 1)
return
if(inventory_head)
if(user)
to_chat(user, span_warning("You can't put more than one hat on [src]!"))
@@ -342,9 +388,8 @@
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(0.1 SECONDS)
dance_rotate(src, set_original_dir = TRUE)
return FALSE
return valid

View File

@@ -118,6 +118,7 @@
/mob/living/simple_animal/parrot/proc/toggle_mode,
/mob/living/simple_animal/parrot/proc/perch_mob_player))
AddElement(/datum/element/strippable, GLOB.strippable_parrot_items)
/mob/living/simple_animal/parrot/examine(mob/user)
. = ..()
@@ -172,91 +173,95 @@
return ITALICS | REDUCE_RANGE
return 0
/*
* Inventory
*/
/mob/living/simple_animal/parrot/show_inv(mob/user)
user.set_machine(src)
var/dat = "<HTML><HEAD><meta charset='UTF-8'></HEAD><BODY><div align='center'><b>Inventory of [name]</b></div><p>"
dat += "<br><B>Headset:</B> <A href='?src=[REF(src)];[ears ? "remove_inv=ears'>[ears]" : "add_inv=ears'>Nothing"]</A></BODY></HTML>"
GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
/datum/strippable_item/parrot_headset,
)))
user << browse(dat, "window=mob[REF(src)];size=325x500")
onclose(user, "window=mob[REF(src)]")
/datum/strippable_item/parrot_headset
key = STRIPPABLE_ITEM_PARROT_HEADSET
/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
if (!istype(equipping, /obj/item/radio/headset))
to_chat(user, "<span class='warning'>[equipping] won't fit!</span>")
return FALSE
return TRUE
/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()
// 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)
return TRUE
/datum/strippable_item/parrot_headset/finish_equip(atom/source, obj/item/equipping, mob/user)
var/obj/item/radio/headset/radio = equipping
if (!istype(radio))
return
var/mob/living/simple_animal/parrot/parrot_source = source
if (!istype(parrot_source))
return
if (!user.transferItemToLoc(radio, 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, span_warning("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)
parrot_source.ears = radio
//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, span_warning("You have nothing in your hand to put on its [add_to]!"))
return
switch(add_to)
if("ears")
if(ears)
to_chat(usr, span_warning("It's already wearing something!"))
return
else
var/obj/item/item_to_add = usr.get_active_held_item()
if(!item_to_add)
return
to_chat(user, "<span class='notice'>You fit [radio] onto [source].</span>")
if( !istype(item_to_add, /obj/item/radio/headset) )
to_chat(usr, span_warning("This object won't fit!"))
return
parrot_source.available_channels.Cut()
var/obj/item/radio/headset/headset_to_add = item_to_add
for (var/channel in radio.channels)
var/channel_to_add
if(!usr.transferItemToLoc(headset_to_add, src))
return
ears = headset_to_add
to_chat(usr, span_notice("You fit the headset onto [src]."))
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
LAZYCLEARLIST(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 (channel_to_add)
parrot_source.available_channels += channel_to_add
if(headset_to_add.translate_binary)
available_channels.Add(MODE_TOKEN_BINARY)
else
return ..()
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
if (!parrot_source.stat)
parrot_source.say("[parrot_source.available_channels.len ? "[pick(parrot_source.available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!")
return TRUE
/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
parrot_source.ears.forceMove(parrot_source.drop_location())
parrot_source.ears = null
/*
* Attack responces

View File

@@ -499,10 +499,6 @@
SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
return TRUE
/// Show the mob's inventory to another mob
/mob/proc/show_inv(mob/user)
return
/**
* Examine a mob
*
@@ -809,10 +805,6 @@
unset_machine()
src << browse(null, t1)
if(href_list["refresh"])
if(machine && in_range(src, usr))
show_inv(machine)
if(href_list["item"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
var/slot = text2num(href_list["item"])
@@ -829,12 +821,6 @@
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)
@@ -859,12 +845,6 @@
if(isAI(M))
return
/mob/MouseDrop_T(atom/dropping, atom/user)
. = ..()
if(ismob(dropping) && dropping != user && user == src)
var/mob/M = dropping
M.show_inv(user)
/**
* Handle the result of a click drag onto this mob
*
@@ -1035,7 +1015,7 @@
is_magic_blocked = TRUE
if((casted_magic_flags & MAGIC_RESISTANCE_HOLY) && HAS_TRAIT(src, TRAIT_HOLY))
is_magic_blocked = TRUE
if(is_magic_blocked && charge_cost > 0 && !HAS_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC))
on_block_magic_effects(casted_magic_flags, antimagic_sources)
@@ -1270,7 +1250,7 @@
return
for(var/atom/movable/screen/plane_master/rendering_plate/lighting/light as anything in hud_used.get_true_plane_masters(RENDER_PLANE_LIGHTING))
light.set_light_cutoff(lighting_cutoff, lighting_color_cutoffs)
///Update the mouse pointer of the attached client in this mob
/mob/proc/update_mouse_pointer()
if (!client)

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

View File

@@ -0,0 +1,413 @@
import { range } from "common/collections";
import { BooleanLike } from "common/react";
import { resolveAsset } from "../assets";
import { useBackend } from "../backend";
import { Box, Button, Icon, Stack } from "../components";
import { Window } from "../layouts";
const ROWS = 5;
const COLUMNS = 6;
const BUTTON_DIMENSIONS = "50px";
type GridSpotKey = string;
const getGridSpotKey = (spot: [number, number]): GridSpotKey => {
return `${spot[0]}/${spot[1]}`;
};
type AlternateAction = {
icon: string;
text: string;
};
const ALTERNATE_ACTIONS: Record<string, AlternateAction> = {
knot: {
icon: "shoe-prints",
text: "Knot",
},
untie: {
icon: "shoe-prints",
text: "Untie",
},
unknot: {
icon: "shoe-prints",
text: "Unknot",
},
enable_internals: {
icon: "mask-ventilator",
text: "Enable internals",
},
disable_internals: {
icon: "wind",
text: "Disable internals",
},
adjust_jumpsuit: {
icon: "tshirt",
text: "Adjust jumpsuit",
},
};
const SLOTS: Record<
string,
{
displayName: string;
gridSpot: GridSpotKey;
image?: string;
additionalComponent?: JSX.Element;
}
> = {
eyes: {
displayName: "eyewear",
gridSpot: getGridSpotKey([0, 1]),
image: "inventory-glasses.png",
},
head: {
displayName: "headwear",
gridSpot: getGridSpotKey([0, 2]),
image: "inventory-head.png",
},
neck: {
displayName: "neckwear",
gridSpot: getGridSpotKey([1, 1]),
image: "inventory-neck.png",
},
mask: {
displayName: "mask",
gridSpot: getGridSpotKey([1, 2]),
image: "inventory-mask.png",
},
corgi_collar: {
displayName: "collar",
gridSpot: getGridSpotKey([1, 2]),
image: "inventory-collar.png",
},
ears: {
displayName: "earwear",
gridSpot: getGridSpotKey([1, 3]),
image: "inventory-ears.png",
},
parrot_headset: {
displayName: "headset",
gridSpot: getGridSpotKey([1, 3]),
image: "inventory-ears.png",
},
handcuffs: {
displayName: "handcuffs",
gridSpot: getGridSpotKey([1, 4]),
},
legcuffs: {
displayName: "legcuffs",
gridSpot: getGridSpotKey([1, 5]),
},
jumpsuit: {
displayName: "uniform",
gridSpot: getGridSpotKey([2, 1]),
image: "inventory-uniform.png",
},
suit: {
displayName: "suit",
gridSpot: getGridSpotKey([2, 2]),
image: "inventory-suit.png",
},
gloves: {
displayName: "gloves",
gridSpot: getGridSpotKey([2, 3]),
image: "inventory-gloves.png",
},
right_hand: {
displayName: "right hand",
gridSpot: getGridSpotKey([2, 4]),
image: "inventory-hand_r.png",
},
left_hand: {
displayName: "left hand",
gridSpot: getGridSpotKey([2, 5]),
image: "inventory-hand_l.png",
},
shoes: {
displayName: "shoes",
gridSpot: getGridSpotKey([3, 2]),
image: "inventory-shoes.png",
},
suit_storage: {
displayName: "suit storage item",
gridSpot: getGridSpotKey([4, 0]),
image: "inventory-suit_storage.png",
},
id: {
displayName: "ID",
gridSpot: getGridSpotKey([4, 1]),
image: "inventory-id.png",
},
belt: {
displayName: "belt",
gridSpot: getGridSpotKey([4, 2]),
image: "inventory-belt.png",
},
back: {
displayName: "backpack",
gridSpot: getGridSpotKey([4, 3]),
image: "inventory-back.png",
},
left_pocket: {
displayName: "left pocket",
gridSpot: getGridSpotKey([4, 4]),
image: "inventory-pocket.png",
},
right_pocket: {
displayName: "right pocket",
gridSpot: getGridSpotKey([4, 5]),
image: "inventory-pocket.png",
},
};
enum ObscuringLevel {
Completely = 1,
Hidden = 2,
}
type Interactable = {
interacting: BooleanLike;
};
/**
* Some possible options:
*
* null - No interactions, no item, but is an available slot
* { interacting: 1 } - No item, but we're interacting with it
* { icon: icon, name: name } - An item with no alternate actions
* that we're not interacting with.
* { icon, name, interacting: 1 } - An item with no alternate actions
* that we're interacting with.
*/
type StripMenuItem =
| null
| Interactable
| ((
| {
icon: string;
name: string;
alternate?: string;
}
| {
obscured: ObscuringLevel;
}
) &
Partial<Interactable>);
type StripMenuData = {
items: Record<keyof typeof SLOTS, StripMenuItem>;
name: string;
};
export const StripMenu = (props, context) => {
const { act, data } = useBackend<StripMenuData>(context);
const gridSpots = new Map<GridSpotKey, string>();
for (const key of Object.keys(data.items)) {
gridSpots.set(SLOTS[key].gridSpot, key);
}
return (
<Window
title={`Stripping ${data.name}`}
width={410}
height={380}>
<Window.Content
style={{
background: "#25252b",
}} >
<Stack fill vertical ml={'10px'}>
{range(0, ROWS).map(row => (
<Stack.Item grow key={row} ml={-2}>
<Stack fill justify="space-around">
{range(0, COLUMNS).map(column => {
const key = getGridSpotKey([row, column]);
const keyAtSpot = gridSpots.get(key);
if (!keyAtSpot) {
return (
<Stack.Item
key={key}
style={{
width: BUTTON_DIMENSIONS,
height: BUTTON_DIMENSIONS,
}}
/>
);
}
const item = data.items[keyAtSpot];
const slot = SLOTS[keyAtSpot];
let alternateAction: AlternateAction | undefined;
let content;
let tooltip;
if (item === null) {
tooltip = slot.displayName;
} else if ("name" in item) {
if (item.alternate) {
alternateAction = ALTERNATE_ACTIONS[item.alternate];
}
content = (
<Box
as="img"
src={`data:image/jpeg;base64,${item.icon}`}
height="64px"
width="64px"
mt={0}
ml={-1}
style={{
"-ms-interpolation-mode": "nearest-neighbor",
"vertical-align": "middle",
}}
/>
);
tooltip = item.name;
} else if ("obscured" in item) {
content = (
<Icon
name={
item.obscured === ObscuringLevel.Completely
? "ban"
: "eye-slash"
}
size={3}
ml={-1}
mt={2.1}
style={{
"text-align": "center",
height: "64px",
width: "64px",
}}
/>
);
tooltip = `obscured ${slot.displayName}`;
}
return (
<Stack.Item
key={key}
style={{
width: BUTTON_DIMENSIONS,
height: BUTTON_DIMENSIONS,
}}
>
<Box
style={{
position: "relative",
width: "64px",
height: "64px",
}}
>
<Button
onClick={() => {
act("use", {
key: keyAtSpot,
});
}}
fluid
tooltip={tooltip}
ml={-0.9}
style={{
background: item?.interacting
? "hsl(39, 73%, 30%)"
: "#25252b",
position: "relative",
width: "64px",
height: "64px",
}}
>
{slot.image && (
<Box
as="img"
src={resolveAsset(slot.image)}
opacity={0.9}
style={{
position: "absolute",
width: "64px",
height: "64px",
left: "50%",
top: "50%",
"-ms-interpolation-mode": "nearest-neighbor",
transform:
"translateX(-50%) translateY(-50%)",
}}
/>
)}
<Box style={{ position: "relative" }}>
{content}
</Box>
{slot.additionalComponent}
</Button>
{alternateAction !== undefined && (
<Button
onClick={() => {
act("alt", {
key: keyAtSpot,
});
}}
tooltip={alternateAction.text}
mr={1}
width={1.6}
height={1.6}
style={{
background: "rgba(0, 0, 0, 0.6)",
position: "absolute",
bottom: 0,
right: 0,
"z-index": 2,
}}
>
<Icon
ml={-0.75}
color={'white'}
name={alternateAction.icon} />
</Button>
)}
</Box>
</Stack.Item>
);
})}
</Stack>
</Stack.Item>
))}
</Stack>
</Window.Content>
</Window>
);
};

View File

@@ -147,6 +147,7 @@
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\station.dm"
#include "code\__DEFINES\status_effects.dm"
#include "code\__DEFINES\strippable.dm"
#include "code\__DEFINES\subsystems.dm"
#include "code\__DEFINES\text.dm"
#include "code\__DEFINES\tgs.config.dm"
@@ -763,6 +764,7 @@
#include "code\datums\elements\rust.dm"
#include "code\datums\elements\speech_bubble_override.dm"
#include "code\datums\elements\squish.dm"
#include "code\datums\elements\strippable.dm"
#include "code\datums\elements\turf_transparency.dm"
#include "code\datums\elements\undertile.dm"
#include "code\datums\elements\update_icon_blocker.dm"
@@ -2875,6 +2877,7 @@
#include "code\modules\mob\living\carbon\carbon_defense.dm"
#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_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"
@@ -2929,6 +2932,7 @@
#include "code\modules\mob\living\carbon\human\human_defines.dm"
#include "code\modules\mob\living\carbon\human\human_helpers.dm"
#include "code\modules\mob\living\carbon\human\human_movement.dm"
#include "code\modules\mob\living\carbon\human\human_stripping.dm"
#include "code\modules\mob\living\carbon\human\inventory.dm"
#include "code\modules\mob\living\carbon\human\life.dm"
#include "code\modules\mob\living\carbon\human\physiology.dm"

View File

@@ -1,4 +1,6 @@
/mob/living/carbon/alien/humanoid/Initialize(mapload, null)
var/datum/action/cooldown/alien/regurgitate/regurgitate = new(src)
regurgitate.Grant(src)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW, 0.5, -11)
AddElement(/datum/element/strippable, GLOB.strippable_alien_humanoid_items)
. = ..()