RPG Loot: Revisited & READY (#82533)

## About The Pull Request
Revival of #72881

A new alt click window with a tarkov-y loading spinner. Replaces the
object item window in stat panel.

## Videos
<details>
<summary>vids</summary>

toggleable grouping:

![syAA5zf6RK](https://github.com/tgstation/tgstation/assets/42397676/c89b372d-29f6-4ebe-895d-f73bbdc41c19)

now lists the floor as first obj:

![abc](https://github.com/tgstation/tgstation/assets/42397676/cd8dc962-2ac7-41bf-a5d3-b9e926116b06)

in action:

![dreamseeker_IkrPKt2QZt](https://github.com/tgstation/tgstation/assets/42397676/1f990aa0-60f0-47e7-9d93-b63e35d05273)

</details>

## features:
- search by name
- 515 image generator is much faster than alt click menu
- opening a gargantuan amount of items shouldnt freeze your screen
- groups similar items together in stacks by default, toggleable
- shows tile as first item
- <kbd>Shift</kbd> and <kbd>Ctrl</kbd> compatible with LMB
🖱️
- RMB points points at items (sry i could not get MMB working)
- key <kbd>Esc</kbd> to exit the window.


For devs:
- A new image generation tech.
- An error refetch mechanic to the Image component
- It does not "smart track" the items being added to the pile, just
reopen or refresh. This was a design decision.

## Why It's Good For The Game
Honestly I just dislike the stat panel

Fixes #53824

Fixes 

![image](https://github.com/tgstation/tgstation/assets/42397676/0e50faab-7d4d-4bf7-8c5b-4ac28547bfbd)

## Changelog
🆑
add: Added a loot window for alt-clicking tiles.
del: Removed the item browser from the stat panel.
/🆑

---------

Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com>
Co-authored-by: AnturK <AnturK@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
This commit is contained in:
Jeremiah
2024-04-14 14:35:03 -07:00
committed by GitHub
parent 0cd28f939c
commit d554ab7766
44 changed files with 875 additions and 254 deletions
+78
View File
@@ -0,0 +1,78 @@
/**
* ## Loot panel
* A datum that stores info containing the contents of a turf.
* Handles opening the lootpanel UI and searching the turf for items.
*/
/datum/lootpanel
/// The owner of the panel
var/client/owner
/// The list of all search objects indexed.
var/list/datum/search_object/contents = list()
/// The list of search_objects needing processed
var/list/datum/search_object/to_image = list()
/// We've been notified about client version
var/notified = FALSE
/// The turf being searched
var/turf/source_turf
/datum/lootpanel/New(client/owner)
. = ..()
src.owner = owner
/datum/lootpanel/Destroy(force)
reset_contents()
owner = null
source_turf = null
return ..()
/datum/lootpanel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LootPanel")
ui.set_autoupdate(FALSE)
ui.open()
/datum/lootpanel/ui_close(mob/user)
. = ..()
source_turf = null
reset_contents()
/datum/lootpanel/ui_data(mob/user)
var/list/data = list()
data["contents"] = get_contents()
data["searching"] = length(to_image)
return data
/datum/lootpanel/ui_status(mob/user, datum/ui_state/state)
if(!source_turf.Adjacent(user))
return UI_CLOSE
if(user.incapacitated())
return UI_DISABLED
return UI_INTERACTIVE
/datum/lootpanel/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("grab")
return grab(usr, params)
if("refresh")
return populate_contents()
return FALSE
+49
View File
@@ -0,0 +1,49 @@
/// Adds the item to contents and to_image (if needed)
/datum/lootpanel/proc/add_to_index(datum/search_object/index)
RegisterSignal(index, COMSIG_QDELETING, PROC_REF(on_searchable_deleted))
if(isnull(index.icon))
to_image += index
contents += index
/// Used to populate contents and start generating if needed
/datum/lootpanel/proc/populate_contents()
if(length(contents))
reset_contents()
// Add source turf first
var/datum/search_object/source = new(owner, source_turf)
add_to_index(source)
for(var/atom/thing as anything in source_turf.contents)
// validate
if(thing.mouse_opacity == MOUSE_OPACITY_TRANSPARENT)
continue
if(thing.IsObscured())
continue
if(thing.invisibility > owner.mob.see_invisible)
continue
// convert
var/datum/search_object/index = new(owner, thing)
add_to_index(index)
var/datum/tgui/window = SStgui.get_open_ui(owner.mob, src)
window?.send_update()
if(length(to_image))
SSlooting.backlog += src
/// For: Resetting to empty. Ignores the searchable qdel event
/datum/lootpanel/proc/reset_contents()
for(var/datum/search_object/index as anything in contents)
contents -= index
to_image -= index
if(QDELETED(index))
continue
UnregisterSignal(index, COMSIG_QDELETING)
qdel(index)
+19
View File
@@ -0,0 +1,19 @@
/// On contents change, either reset or update
/datum/lootpanel/proc/on_searchable_deleted(datum/search_object/source)
SIGNAL_HANDLER
contents -= source
to_image -= source
var/datum/tgui/window = SStgui.get_open_ui(owner.mob, src)
#if !defined(UNIT_TESTS) // we dont want to delete contents if we're testing
if(isnull(window))
reset_contents()
return
#endif
if(isturf(source.item))
populate_contents()
return
window?.send_update()
+48
View File
@@ -0,0 +1,48 @@
/// Helper to open the panel
/datum/lootpanel/proc/open(turf/tile)
source_turf = tile
#if !defined(OPENDREAM) && !defined(UNIT_TESTS)
if(!notified)
var/build = owner.byond_build
var/version = owner.byond_version
if(build < 515 || (build == 515 && version < 1635))
to_chat(owner.mob, examine_block(span_info("\
<span class='bolddanger'>Your version of Byond doesn't support fast image loading.</span>\n\
Detected: [version].[build]\n\
Required version for this feature: <b>515.1635</b> or later.\n\
Visit <a href=\"https://secure.byond.com/download\">BYOND's website</a> to get the latest version of BYOND.\n\
")))
notified = TRUE
#endif
populate_contents()
ui_interact(owner.mob)
/**
* Called by SSlooting whenever this datum is added to its backlog.
* Iterates over to_image list to create icons, then removes them.
* Returns boolean - whether this proc has finished the queue or not.
*/
/datum/lootpanel/proc/process_images()
for(var/datum/search_object/index as anything in to_image)
to_image -= index
if(QDELETED(index) || index.icon)
continue
index.generate_icon(owner)
if(TICK_CHECK)
break
var/datum/tgui/window = SStgui.get_open_ui(owner.mob, src)
if(isnull(window))
reset_contents()
return TRUE
window.send_update()
return !length(to_image)
+84
View File
@@ -0,0 +1,84 @@
/**
* ## Search Object
* An object for content lists. Compacted item data.
*/
/datum/search_object
/// Item we're indexing
var/atom/item
/// Url to the image of the object
var/icon
/// Icon state, for inexpensive icons
var/icon_state
/// Name of the original object
var/name
/// Typepath of the original object for ui grouping
var/path
/datum/search_object/New(client/owner, atom/item)
. = ..()
src.item = item
name = item.name
if(isobj(item))
path = item.type
if(isturf(item))
RegisterSignal(item, COMSIG_TURF_CHANGE, PROC_REF(on_turf_change))
else
RegisterSignals(item, list(
COMSIG_ITEM_PICKUP,
COMSIG_MOVABLE_MOVED,
COMSIG_QDELETING,
), PROC_REF(on_item_moved))
// Icon generation conditions //////////////
// Condition 1: Icon is complex
if(ismob(item) || length(item.overlays) > 2)
return
// Condition 2: Can't get icon path
if(!isfile(item.icon) || !length("[item.icon]"))
return
// Condition 3: Using opendream
#if defined(OPENDREAM) || defined(UNIT_TESTS)
return
#endif
// Condition 4: Using older byond version
var/build = owner.byond_build
var/version = owner.byond_version
if(build < 515 || (build == 515 && version < 1635))
return
icon = "[item.icon]"
icon_state = item.icon_state
/datum/search_object/Destroy(force)
item = null
return ..()
/// Generates the icon for the search object. This is the expensive part.
/datum/search_object/proc/generate_icon(client/owner)
if(ismob(item) || length(item.overlays) > 2)
icon = costly_icon2html(item, owner, sourceonly = TRUE)
else // our pre 515.1635 fallback for normal items
icon = icon2html(item, owner, sourceonly = TRUE)
/// Parent item has been altered, search object no longer valid
/datum/search_object/proc/on_item_moved(atom/source)
SIGNAL_HANDLER
qdel(src)
/// Parent tile has been altered, entire search needs reset
/datum/search_object/proc/on_turf_change(turf/source, path, list/new_baseturfs, flags, list/post_change_callbacks)
SIGNAL_HANDLER
post_change_callbacks += CALLBACK(src, GLOBAL_PROC_REF(qdel), src)
+39
View File
@@ -0,0 +1,39 @@
/// Queues image generation for search objects without icons
SUBSYSTEM_DEF(looting)
name = "Loot Icon Generation"
init_order = INIT_ORDER_LOOT
priority = FIRE_PRIORITY_PROCESS
wait = 0.5 SECONDS
/// Backlog of items. Gets put into processing
var/list/datum/lootpanel/backlog = list()
/// Actively processing items
var/list/datum/lootpanel/processing = list()
/datum/controller/subsystem/looting/stat_entry(msg)
msg = "P:[length(backlog)]"
return ..()
/datum/controller/subsystem/looting/fire(resumed)
if(!length(backlog))
return
if(!resumed)
processing = backlog
backlog = list()
while(length(processing))
var/datum/lootpanel/panel = processing[length(processing)]
if(QDELETED(panel) || !length(panel.to_image))
processing.len--
continue
if(!panel.process_images())
backlog += panel
if(MC_TICK_CHECK)
return
processing.len--
+46
View File
@@ -0,0 +1,46 @@
/// UI helper for converting the associative list to a list of lists
/datum/lootpanel/proc/get_contents()
var/list/items = list()
for(var/datum/search_object/index as anything in contents)
UNTYPED_LIST_ADD(items, list(
"icon_state" = index.icon_state,
"icon" = index.icon,
"name" = index.name,
"path" = index.path,
"ref" = REF(index),
))
return items
/// Clicks an object from the contents. Validates the object and the user
/datum/lootpanel/proc/grab(mob/user, list/params)
var/ref = params["ref"]
if(isnull(ref))
return FALSE
if(!source_turf.Adjacent(user)) // Source tile is no longer valid
reset_contents()
return FALSE
var/datum/search_object/index = locate(ref) in contents
var/atom/thing = index?.item
if(QDELETED(index) || QDELETED(thing)) // Obj is gone
return FALSE
if(thing != source_turf && !(locate(thing) in source_turf.contents))
qdel(index) // Item has moved
return TRUE
var/modifiers = ""
if(params["ctrl"])
modifiers += "ctrl=1;"
if(params["middle"])
modifiers += "middle=1;"
if(params["shift"])
modifiers += "shift=1;"
user.ClickOn(thing, modifiers)
return TRUE