mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-21 03:56:47 +01:00
Replace the alt click menu with the RPG Lootpanel (#17938)
* Port lootpanel without removing old obj panel * Rip out the loot panel leaving examine tab intact * some fixes * we want nice icons * that * Switch to more robust hotkey detection * Add a reminder to ctrl-r --------- Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* ## 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/tgui_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/tgui_close(mob/user)
|
||||
. = ..()
|
||||
|
||||
source_turf = null
|
||||
reset_contents()
|
||||
|
||||
|
||||
/datum/lootpanel/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["contents"] = get_contents()
|
||||
data["is_blind"] = !!user.is_blind()
|
||||
data["searching"] = length(to_image)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
/datum/lootpanel/tgui_status(mob/user, datum/tgui_state/state)
|
||||
// note: different from /tg/, we prohibit non-viewers from trying to update the window and close it automatically for them
|
||||
if(!(user in viewers(source_turf)))
|
||||
return STATUS_CLOSE
|
||||
|
||||
if(user.incapacitated())
|
||||
return STATUS_DISABLED
|
||||
|
||||
return STATUS_INTERACTIVE
|
||||
|
||||
|
||||
/datum/lootpanel/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("grab")
|
||||
return grab(usr, params)
|
||||
if("refresh")
|
||||
return populate_contents()
|
||||
|
||||
return FALSE
|
||||
@@ -0,0 +1,54 @@
|
||||
/// Adds the item to contents and to_image (if needed)
|
||||
/datum/lootpanel/proc/add_to_index(datum/search_object/index)
|
||||
RegisterSignal(index, COMSIG_PARENT_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(!istype(thing))
|
||||
stack_trace("Non-atom in the contents of [source_turf]!")
|
||||
continue
|
||||
if(QDELETED(thing))
|
||||
continue
|
||||
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_PARENT_QDELETING)
|
||||
qdel(index)
|
||||
@@ -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()
|
||||
@@ -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, 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()
|
||||
tgui_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)
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* ## 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
|
||||
// Lest we find ourselves here again, this is intentionally stupid.
|
||||
// It tracks items going out and user actions, otherwise they can refresh the lootpanel.
|
||||
// If this is to be made to track everything, we'll need to make a new signal to specifically create/delete a search object
|
||||
RegisterSignals(item, list(
|
||||
COMSIG_ITEM_PICKUP,
|
||||
COMSIG_MOVABLE_MOVED,
|
||||
COMSIG_PARENT_QDELETING,
|
||||
), PROC_REF(on_item_moved))
|
||||
|
||||
// Icon generation conditions //////////////
|
||||
// Condition 1: Icon is complex
|
||||
if(ismob(item) || length(item.overlays) > 0)
|
||||
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))
|
||||
icon = "n/a"
|
||||
return
|
||||
|
||||
icon = "[item.icon]"
|
||||
icon_state = item.icon_state
|
||||
|
||||
|
||||
/datum/search_object/Destroy(force)
|
||||
item = null
|
||||
icon = null
|
||||
|
||||
return ..()
|
||||
|
||||
|
||||
/// Generates the icon for the search object. This is the expensive part.
|
||||
/datum/search_object/proc/generate_icon(client/owner)
|
||||
icon = costly_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
|
||||
|
||||
if(QDELETED(src))
|
||||
return
|
||||
|
||||
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(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), src)
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
/// Queues image generation for search objects without icons
|
||||
SUBSYSTEM_DEF(looting)
|
||||
name = "Loot Icon Generation"
|
||||
flags = SS_NO_INIT
|
||||
priority = FIRE_PRIORITY_PROCESS
|
||||
runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
|
||||
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--
|
||||
@@ -0,0 +1,47 @@
|
||||
/// 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
|
||||
|
||||
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;"
|
||||
if(params["alt"])
|
||||
modifiers += "alt=1;"
|
||||
if(params["right"])
|
||||
modifiers += "right=1;"
|
||||
|
||||
|
||||
user.ClickOn(thing, modifiers)
|
||||
|
||||
return TRUE
|
||||
Reference in New Issue
Block a user