TGUI-fies the Spawn verb (#94094)

## About The Pull Request

This PR converts the native text/list input that pops up when the Spawn
verb is used into a TGUI panel with a dynamic search function. It now
displays atom names, as well if they are abstract (and probably
shouldn't be spawned).
You can still input a typepath after the spawn verb in the tab bar which
will open the menu with said path already in the input box. If only one
typepath matches the input, it will be spawned immediately without
opening the panel.

<img width="400" height="500" alt="Qs0t8xkGj7"
src="https://github.com/user-attachments/assets/d54a57d5-fc22-4f59-af18-45b4fab0b256"
/>

It features RegEx support which activates when the input starts with
``re:``, as well as toggles for searching in atom names and fancy
typepath display. They can be toggled via buttons, or Alt+R/N/F for
regex/names/fancy paths respectively. Invalid regex-es will highlight
the search bar red.
The list itself can be navigated using arrow keys, enter/double click
will spawn the atom, and escape will close the menu.

<img width="400" height="500" alt="xgK2vCd1ck"
src="https://github.com/user-attachments/assets/e621c6ba-ad6b-4a30-922f-eb863797c65a"
/>

<img width="400" height="500" alt="2DeOPBRqoC"
src="https://github.com/user-attachments/assets/18fbba32-17d7-4351-bbc2-bc8e66eb24ae"
/>

Old functionality of spawning multiple objects by leaving a colon +
number after the typepath has been preserved. Using ``*`` and``!`` to
signify final path/end of a path respectively also still works when
regex mode isn't active.

## Why It's Good For The Game

Spawn isn't a critical tool, and using TGUI allows it to have dynamic
search and shifts searching from serverside to clientside, making it
significantly more responsive. Because we don't need to build a fancy
list of atom types, using it the first time won't cause a lag spike on
weaker machines anymore, which should make debugging locally a bit less
annoying.

## Changelog
🆑
admin: Converted the Spawn verb into a TGUI input, featuring for dynamic
search and RegEx support
/🆑
This commit is contained in:
SmArtKar
2025-11-28 15:52:28 -07:00
committed by GitHub
parent 486ef6ebf1
commit fd06311361
8 changed files with 627 additions and 160 deletions
+3 -40
View File
@@ -15,14 +15,10 @@
#define OFFSET_ABSOLUTE "Absolute offset"
#define OFFSET_RELATIVE "Relative offset"
GLOBAL_LIST_INIT(spawnpanels_by_ckey, list())
/*
An instance of a /tg/UI™ Spawn Panel. Stores preferences, spawns things, controls the UI. Unique for each user (their ckey).
*/
/datum/spawnpanel
/// Who this instance of spawn panel belongs to. The instances are unique to correctly keep modified values between multiple admins.
var/owner_ckey
/// Where and how the atom should be spawned.
var/where_target_type = WHERE_FLOOR_BELOW_MOB
/// The atom selected from the panel.
@@ -57,45 +53,21 @@ GLOBAL_LIST_INIT(spawnpanels_by_ckey, list())
offset = list("X" = 0, "Y" = 0, "Z" = 0)
/datum/spawnpanel/ui_interact(mob/user, datum/tgui/ui)
if(user.client.ckey != owner_ckey)
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SpawnPanel")
ui.open()
/// Returns a `spawnpanel` instance belonging to this `user`, or creates and registers a new one
/proc/get_spawnpanel_for_admin(mob/user)
if(!user?.client?.ckey)
return null
var/ckey = user.client.ckey
if(GLOB.spawnpanels_by_ckey[ckey])
return GLOB.spawnpanels_by_ckey[ckey]
var/datum/spawnpanel/new_panel = new()
new_panel.owner_ckey = ckey
GLOB.spawnpanels_by_ckey[ckey] = new_panel
return new_panel
/datum/spawnpanel/ui_close(mob/user)
. = ..()
if (precise_mode && precise_mode != PRECISE_MODE_OFF)
toggle_precise_mode(PRECISE_MODE_OFF)
/datum/spawnpanel/ui_state(mob/user)
if(user.client.ckey != owner_ckey)
return GLOB.never_state
return ADMIN_STATE(R_ADMIN)
return ADMIN_STATE(R_SPAWN)
/datum/spawnpanel/ui_act(action, params)
if(..())
return FALSE
if(usr.client.ckey != owner_ckey)
/datum/spawnpanel/ui_act(action, params, datum/tgui/ui)
if(..() || !check_rights_for(ui.user.client, R_SPAWN))
return FALSE
switch(action)
@@ -106,12 +78,10 @@ GLOBAL_LIST_INIT(spawnpanels_by_ckey, list())
available_icon_states = icon_states(selected_atom_icon)
if(!(selected_atom_icon_state in available_icon_states))
selected_atom_icon_state = available_icon_states[1]
SStgui.update_uis(src)
return TRUE
if("set-apply-icon-override")
apply_icon_override = !!params["value"]
SStgui.update_uis(src)
return TRUE
if("reset-DMI-icon")
@@ -123,35 +93,28 @@ GLOBAL_LIST_INIT(spawnpanels_by_ckey, list())
available_icon_states = icon_states(selected_atom_icon)
else
available_icon_states = list()
SStgui.update_uis(src)
return TRUE
if("select-new-icon-state")
selected_atom_icon_state = params["new_state"]
SStgui.update_uis(src)
return TRUE
if("reset-icon-state")
selected_atom_icon_state = null
if(selected_atom)
selected_atom_icon_state = initial(selected_atom.icon_state)
SStgui.update_uis(src)
return TRUE
if("set-icon-size")
atom_icon_size = params["size"]
SStgui.update_uis(src)
return TRUE
if("reset-icon-size")
atom_icon_size = 100
SStgui.update_uis(src)
return TRUE
if("get-icon-states")
available_icon_states = icon_states(selected_atom_icon)
SStgui.update_uis(src)
return TRUE
if("selected-atom-changed")