mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-29 02:51:41 +00:00
* Adds non-clothing item equipping others feedback messages (#73982) ## About The Pull Request Things like pens weren't giving any feedback messages when you put them on someone else, and I ran into this while working on another PR so I've dealt with that Renames `worn_dangerous` to `show_visible_message` as it was only used to confirm if there would be visible messages or not The `DANGEROUS_OBJECT` clothing flag is a trait now, so it can be put on non-clothing items too Removing non-clothing items from someone has been unchanged. ## Why It's Good For The Game People should be able to identify that someone is putting something on them, and recognize what that is if they pay attention. This means that a player cannot reverse pickpocket a grenade onto someone else without giving any indication of doing so ## Changelog 🆑 balance: Putting a non-clothing item onto someone else creates a visible message the same way a clothing item would. /🆑 * Adds non-clothing item equipping others feedback messages --------- Co-authored-by: ArcaneDefence <51932756+ArcaneDefence@users.noreply.github.com>
503 lines
15 KiB
Plaintext
503 lines
15 KiB
Plaintext
/// 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_ON_HOST_DESTROY
|
|
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)
|
|
. = ..()
|
|
|
|
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.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 give feedback messages?
|
|
var/show_visible_message = TRUE
|
|
|
|
/// Can it be silent?
|
|
var/can_be_silent = FALSE //SKYRAT EDIT ADDITION - THIEVING GLOVES
|
|
|
|
/// 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(SEND_SIGNAL(user, COMSIG_TRY_STRIP, source, equipping) & COMPONENT_CANT_STRIP)
|
|
return FALSE
|
|
|
|
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)
|
|
|
|
equipping.item_start_equip(source, equipping, user, show_visible_message)
|
|
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))
|
|
if(SEND_SIGNAL(user, COMSIG_TRY_STRIP, source, item) & COMPONENT_CANT_STRIP)
|
|
return FALSE
|
|
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
|
|
|
|
//SKYRAT EDIT CHANGE START - THIEVING GLOVES
|
|
var/is_silent = can_be_silent && HAS_TRAIT(user, TRAIT_STICKY_FINGERS)
|
|
if (!is_silent)
|
|
source.visible_message(
|
|
span_warning("[user] tries to remove [source]'s [item.name]."),
|
|
span_userdanger("[user] tries to remove your [item.name]."),
|
|
blind_message = span_hear("You hear rustling."),
|
|
ignored_mobs = user,
|
|
)
|
|
//SKYRAT EDIT CHANGE END
|
|
|
|
to_chat(user, span_danger("You try to remove [source]'s [item.name]..."))
|
|
user.log_message("is stripping [key_name(source)] of [item].", LOG_ATTACK, color="red")
|
|
source.log_message("is being stripped of [item] by [key_name(user)].", LOG_VICTIM, color="orange", log_globally=FALSE)
|
|
item.add_fingerprint(src)
|
|
|
|
if(ishuman(source) && !is_silent) //SKYRAT EDIT ADDITION - THIEVING GLOVES ORIGINAL 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)
|
|
|
|
else if(victim_human.is_blind())
|
|
to_chat(source, span_userdanger("You feel someone fumble with your belongings."))
|
|
|
|
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.
|
|
/// Returns FALSE if blocked by signal, TRUE otherwise.
|
|
/datum/strippable_item/proc/alternate_action(atom/source, mob/user)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(SEND_SIGNAL(user, COMSIG_TRY_ALT_ACTION, source) & COMPONENT_CANT_ALT_ACTION)
|
|
return FALSE
|
|
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, 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_after(user, get_equip_delay(equipping), source))
|
|
return FALSE
|
|
|
|
if (!equipping.mob_can_equip(source, 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)
|
|
|
|
return finish_equip_mob(equipping, source, user)
|
|
|
|
/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 finish equipping an item to a mob.
|
|
/proc/finish_equip_mob(obj/item/item, mob/source, mob/user)
|
|
user.log_message("has put [item] on [key_name(source)].", LOG_ATTACK, color="red")
|
|
source.log_message("had [item] put on them by [key_name(user)].", LOG_VICTIM, color="orange", log_globally=FALSE)
|
|
|
|
/// 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)
|
|
//SKYRAT EDIT ADDITION - THIEVING GLOVES
|
|
//if (!do_after(user, strip_delay || item.strip_delay, source, interaction_key = REF(item)))
|
|
if (!do_after(user, (strip_delay || item.strip_delay) * (HAS_TRAIT(user, TRAIT_STICKY_FINGERS) ? THIEVING_GLOVES_STRIP_SLOWDOWN : NORMAL_STRIP_SLOWDOWN), 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
|
|
|
|
user.log_message("has stripped [key_name(source)] of [item].", LOG_ATTACK, color="red")
|
|
source.log_message("has been stripped of [item] by [key_name(user)].", LOG_VICTIM, color="orange", 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) || (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)))
|
|
user.put_in_hands(held_item)
|
|
return
|
|
|
|
if (!user.Adjacent(owner))
|
|
user.put_in_hands(held_item)
|
|
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)
|
|
return min(
|
|
ui_status_only_living(user, owner),
|
|
ui_status_user_has_free_hands(user, owner),
|
|
ui_status_user_is_adjacent(user, owner, allow_tk = FALSE),
|
|
HAS_TRAIT(user, TRAIT_CAN_STRIP) ? UI_INTERACTIVE : UI_UPDATE,
|
|
max(
|
|
ui_status_user_is_conscious_and_lying_down(user),
|
|
ui_status_user_is_abled(user, owner),
|
|
),
|
|
)
|
|
|
|
/// 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
|