Ports and Adapts TGUI strip panel from TG (#24618)

* part 1

* these also

* fuckin nuke it

* more

* more stuff

* more crap

* wow

* holy fucking shit, it works

* thats a start

* oops

* fubing bundle

* hahah brrrrr

* I LITERALLY ADDED IT BACK, FUCK OFF GIT

* yeah more changes

* hide abstract

* blamo

* thats enough for today

* old code begone

* goodbye can_strip

* nearly there

* finally

* fuckin finally

* review

* tgui

* unbreak shit

* fix OD

* okay, that should fix the last thing

* remove hover on nodrop, fix early interacting
This commit is contained in:
Contrabang
2024-04-08 21:40:11 +00:00
committed by GitHub
parent a50dc50849
commit 744d9c6bed
67 changed files with 1800 additions and 824 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
* Normal squishes apply vertically, as if the target is being squished from above, but you can set reverse to TRUE if you want to squish them from the sides, like if they pancake into a wall from the East or West
*/
/datum/element/squish
element_flags = ELEMENT_DETACH
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
/datum/element/squish/Attach(datum/target, duration = 20 SECONDS, reverse = FALSE)
. = ..()
+1 -1
View File
@@ -22,7 +22,7 @@
if(type == /datum/element)
return ELEMENT_INCOMPATIBLE
SEND_SIGNAL(target, COMSIG_ELEMENT_ATTACH, src)
if(element_flags & ELEMENT_DETACH)
if(element_flags & ELEMENT_DETACH_ON_HOST_DESTROY)
RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(Detach), override = TRUE)
/// Deactivates the functionality defines by the element on the given datum
+1 -1
View File
@@ -1,5 +1,5 @@
/datum/element/earhealing
element_flags = ELEMENT_DETACH
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
var/list/user_by_item = list()
/datum/element/earhealing/New()
+1 -1
View File
@@ -1,5 +1,5 @@
/datum/element/rad_insulation
element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY | ELEMENT_BESPOKE
id_arg_index = 2
var/amount // Multiplier for radiation strength passing through
+487
View File
@@ -0,0 +1,487 @@
/// 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
id_arg_index = 2
/// An assoc list of keys to /datum/strippable_item
var/list/items
/// An existing strip menus
var/list/strip_menus
/datum/element/strippable/Attach(datum/target, list/items = list())
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_DO_MOB_STRIP, PROC_REF(mouse_drop_onto))
src.items = items
/datum/element/strippable/Detach(datum/source)
. = ..()
UnregisterSignal(source, COMSIG_DO_MOB_STRIP)
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
var/datum/strip_menu/strip_menu = LAZYACCESS(strip_menus, source)
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
/// Gets the item from the given source.
/datum/strippable_item/proc/get_item(atom/source)
return
/// 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(equipping.flags & NODROP)
to_chat(user, "<span class='warning'>You can't put [equipping] on [source], it's stuck to your hand!</span>")
return FALSE
if(equipping.flags & ABSTRACT)
return FALSE //I don't know a sane-sounding feedback message for trying to put a slap into someone's hand
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)
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>",
)
if(ishuman(source))
var/mob/living/carbon/human/victim_human = source
if(!victim_human.has_vision())
to_chat(victim_human, "<span class='userdanger'>You feel someone trying to put something on you.</span>")
if(!do_mob(user, source, equipping.put_on_delay))
return FALSE
if(QDELETED(equipping) || !user.Adjacent(source) || (equipping.flags & NODROP))
return FALSE
return TRUE
/// The proc that places the item on the source. This should not yield.
/datum/strippable_item/proc/finish_equip(atom/source, obj/item/equipping, mob/user)
SHOULD_NOT_SLEEP(TRUE)
return
/// Tries to unequip the item from the given source.
/// Returns TRUE/FALSE depending on if it is allowed.
/// This should be used for checking if it CAN be unequipped.
/// It should not perform the unequipping itself.
/datum/strippable_item/proc/try_unequip(atom/source, mob/user)
SHOULD_NOT_SLEEP(TRUE)
var/obj/item/item = get_item(source)
if(isnull(item))
return FALSE
if(ismob(source))
var/mob/mob_source = source
if(!item.canStrip(user, mob_source))
return FALSE
return TRUE
/// Start the unequipping process. This is the proc you should yield in.
/// Returns TRUE/FALSE depending on if it is allowed.
/datum/strippable_item/proc/start_unequip(atom/source, mob/user)
var/obj/item/item = get_item(source)
if(isnull(item))
return FALSE
source.visible_message(
"<span class='warning'>[user] tries to remove [source]'s [item.name].</span>",
"<span class='userdanger'>[user] tries to remove your [item.name].</span>",
"You hear rustling."
)
to_chat(user, "<span class='danger'>You try to remove [source]'s [item.name]...</span>")
add_attack_logs(user, source, "Attempting stripping of [item]")
item.add_fingerprint(user)
if(ishuman(source))
var/mob/living/carbon/human/victim_human = source
if(!victim_human.has_vision())
to_chat(source, "<span class='userdanger'>You feel someone fumble with your belongings.</span>")
return start_unequip_mob(get_item(source), source, user)
/// The proc that unequips the item from the source. This should not yield.
/datum/strippable_item/proc/finish_unequip(atom/source, mob/user)
SHOULD_NOT_SLEEP(TRUE)
return
/// 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_actions(atom/source, mob/user)
return null
/**
* Actions that can happen to that body part, regardless if there is an item or not. As long as it is not obscured
*/
/datum/strippable_item/proc/get_body_action(atom/source, mob/user)
return
/// 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, action_key)
SHOULD_CALL_PARENT(TRUE)
return TRUE
/// Returns whether or not this item should show.
/datum/strippable_item/proc/should_show(atom/source, mob/user)
return TRUE
/// Returns whether or not this item should show.
/datum/strippable_item/proc/in_thief_mode(mob/user)
if(!ishuman(user))
return FALSE
var/mob/living/carbon/human/H = usr
var/obj/item/clothing/gloves/G = H.gloves
return G?.pickpocket
/// A preset for equipping items onto mob slots
/datum/strippable_item/mob_item_slot
/// The SLOT_FLAG_* 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))
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(!equipping.mob_can_equip(source, item_slot, disable_warning = TRUE))
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)
add_attack_logs(user, source, "Strip equipped [equipping]")
/datum/strippable_item/mob_item_slot/get_obscuring(atom/source)
if(ishuman(source))
var/mob/living/carbon/human/human_source = source
if(item_slot in human_source.check_obscured_slots())
return STRIPPABLE_OBSCURING_COMPLETELY
return STRIPPABLE_OBSCURING_NONE
return FALSE
/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
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(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.put_on_delay
/// 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(!strip_delay)
strip_delay = item.strip_delay
if(!do_mob(user, source, strip_delay))
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(!source.unEquip(item))
return
add_attack_logs(user, source, "Stripping of [item]")
/// 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
var/obj/item/item = item_data.get_item(owner)
if(item && (item.flags & ABSTRACT))
items[strippable_key] = result
continue
if(strippable_key in LAZYACCESS(interactions, user))
LAZYSET(result, "interacting", TRUE)
var/obscuring = item_data.get_obscuring(owner)
if(obscuring == STRIPPABLE_OBSCURING_COMPLETELY || (item && !item.canStrip(user)))
LAZYSET(result, "cantstrip", TRUE)
if(obscuring != STRIPPABLE_OBSCURING_NONE)
LAZYSET(result, "obscured", obscuring)
items[strippable_key] = result
continue
var/alternates = item_data.get_body_action(owner, user)
if(!islist(alternates) && !isnull(alternates))
alternates = list(alternates)
if(isnull(item))
if(length(alternates))
LAZYSET(result, "alternates", alternates)
items[strippable_key] = result
continue
LAZYINITLIST(result)
result["icon"] = icon2base64(icon(item.icon, item.icon_state, dir = SOUTH, frame = 1, moving = FALSE))
result["name"] = item.name
var/real_alts = item_data.get_alternate_actions(owner, user)
if(!isnull(real_alts))
if(islist(alternates))
alternates += real_alts
else
alternates = real_alts
if(!islist(alternates) && !isnull(alternates))
alternates = list(alternates)
result["alternates"] = alternates
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
var/mob/living/user = ui.user
if(!isliving(ui.user) || !HAS_TRAIT(user, TRAIT_CAN_STRIP))
return
. = TRUE
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_hand()
if(isnull(held_item))
return
if(strippable_item.try_equip(owner, held_item, user))
LAZYORASSOCLIST(interactions, user, key)
// Update just before the delay starts
SStgui.update_uis(src)
// 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, or they're no longer adjacent
if(!isnull(strippable_item.get_item(owner)) || !user.Adjacent(owner))
return
// make sure to drop the item
if(!user.unEquip(held_item))
return
strippable_item.finish_equip(owner, held_item, user)
else if(strippable_item.try_unequip(owner, user))
LAZYORASSOCLIST(interactions, user, key)
// Update just before the delay starts
SStgui.update_uis(src)
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
if(isnull(strippable_item.get_body_action(owner, user)))
var/item = strippable_item.get_item(owner)
if(isnull(item) || isnull(strippable_item.get_alternate_actions(owner, user)))
return
LAZYORASSOCLIST(interactions, user, key)
// Update just before the delay starts
SStgui.update_uis(src)
// Potentially yielding
strippable_item.alternate_action(owner, user, params["action_key"])
LAZYREMOVEASSOC(interactions, user, key)
/datum/strip_menu/ui_host(mob/user)
return owner
/datum/strip_menu/ui_state(mob/user)
return GLOB.strippable_state
/// 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