Files
Bubberstation/code/modules/admin/admin.dm
T
SmArtKar fd06311361 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
/🆑
2025-11-28 15:52:28 -07:00

180 lines
6.2 KiB
Plaintext

////////////////////////////////
/proc/message_admins(msg)
msg = "<span class=\"admin\"><span class=\"prefix\">ADMIN LOG:</span> <span class=\"message\">[msg]</span></span>"
to_chat(GLOB.admins,
type = MESSAGE_TYPE_ADMINLOG,
html = msg,
confidential = TRUE)
/proc/relay_msg_admins(msg)
msg = "<span class=\"admin\"><span class=\"prefix\">RELAY:</span> <span class=\"message\">[msg]</span></span>"
to_chat(GLOB.admins,
type = MESSAGE_TYPE_ADMINLOG,
html = msg,
confidential = TRUE)
///////////////////////////////////////////////////////////////////////////////////////////////Panels
/datum/admins/proc/Game()
if(!check_rights(0))
return
var/dat
dat += "<a href='byond://?src=[REF(src)];[HrefToken()];gamemode_panel=1'>Dynamic Panel</a><BR>"
dat += "<hr/>"
dat += "<a href='byond://?src=[REF(src)];[HrefToken()];spawn_panel=1'>Spawn Panel</a><br>"
if(marked_datum && istype(marked_datum, /atom))
dat += "<a href='byond://?src=[REF(src)];[HrefToken()];dupe_marked_datum=1'>Duplicate Marked Datum</a><br>"
var/datum/browser/browser = new(usr, "admin2", "Game Panel", 240, 280)
browser.set_content(dat)
browser.open()
return
////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS
ADMIN_VERB(spawn_atom, R_SPAWN, "Spawn", "Spawn an atom.", ADMIN_CATEGORY_DEBUG, object as text|null)
var/static/list/atom_types
if (isnull(atom_types))
atom_types = subtypesof(/atom)
var/chosen_path = null
var/list/preparsed = null
if (object)
preparsed = splittext(object, ":")
var/list/matches = filter_fancy_list(atom_types, preparsed[1])
if (length(matches) == 1)
chosen_path = matches[1]
if(!chosen_path)
var/datum/spawn_menu/menu = user.holder.spawn_menu
if (!menu)
menu = new()
user.holder.spawn_menu = menu
menu.init_value = object
menu.ui_interact(user.mob)
BLACKBOX_LOG_ADMIN_VERB("Spawn Atom")
return TRUE
var/amount = 1
if (length(preparsed) > 1)
amount = clamp(text2num(preparsed[2]), 1, ADMIN_SPAWN_CAP)
var/turf/target_turf = get_turf(user.mob)
if (ispath(chosen_path, /turf))
target_turf.ChangeTurf(chosen_path)
else
for (var/i in 1 to amount)
var/atom/spawned = new chosen_path(target_turf)
spawned.flags_1 |= ADMIN_SPAWNED_1
log_admin("[key_name(user.mob)] spawned [amount] x [chosen_path] at [AREACOORD(user.mob)]")
BLACKBOX_LOG_ADMIN_VERB("Spawn Atom")
return TRUE
ADMIN_VERB(spawn_atom_pod, R_SPAWN, "PodSpawn", "Spawn an atom via supply drop.", ADMIN_CATEGORY_DEBUG, object as text)
var/chosen = pick_closest_path(object)
if(!chosen)
return
var/turf/target_turf = get_turf(user.mob)
if(ispath(chosen, /turf))
target_turf.ChangeTurf(chosen)
else
var/obj/structure/closet/supplypod/pod = podspawn(list(
"target" = target_turf,
"path" = /obj/structure/closet/supplypod/centcompod,
))
//we need to set the admin spawn flag for the spawned items so we do it outside of the podspawn proc
var/atom/A = new chosen(pod)
A.flags_1 |= ADMIN_SPAWNED_1
log_admin("[key_name(user)] pod-spawned [chosen] at [AREACOORD(user.mob)]")
BLACKBOX_LOG_ADMIN_VERB("Podspawn Atom")
ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CATEGORY_DEBUG, object as text)
var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/datum/supply_pack)))
if(!chosen)
return
var/datum/supply_pack/S = new chosen
S.admin_spawned = TRUE
S.generate(get_turf(user.mob))
log_admin("[key_name(user)] spawned cargo pack [chosen] at [AREACOORD(user.mob)]")
BLACKBOX_LOG_ADMIN_VERB("Spawn Cargo")
ADMIN_VERB(create_or_modify_area, R_DEBUG, "Create Or Modify Area", "Create of modify an area. wow.", ADMIN_CATEGORY_DEBUG)
create_area(user.mob)
//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked
//defaults to kicking everyone (afk + non afk clients in the lobby)
//returns a list of ckeys of the kicked clients
/proc/kick_clients_in_lobby(message, kick_only_afk = 0)
var/list/kicked_client_names = list()
for(var/client/C in GLOB.clients)
if(isnewplayer(C.mob))
if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk
continue
if(message)
to_chat(C, message, confidential = TRUE)
kicked_client_names.Add("[C.key]")
qdel(C)
return kicked_client_names
//returns TRUE to let the dragdrop code know we are trapping this event
//returns FALSE if we don't plan to trap the event
/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob)
//this is the exact two check rights checks required to edit a ckey with vv.
if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0))
return FALSE
if (!frommob.ckey)
return FALSE
var/question = ""
if (tomob.ckey)
question = "This mob already has a user ([tomob.key]) in control of it! "
question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?"
var/ask = tgui_alert(usr, question, "Place ghost in control of mob?", list("Yes", "No"))
if (ask != "Yes")
return TRUE
if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response
return TRUE
// Disassociates observer mind from the body mind
if(tomob.client)
tomob.ghostize(FALSE)
else
for(var/mob/dead/observer/ghost in GLOB.dead_mob_list)
if(tomob.mind == ghost.mind)
ghost.mind = null
message_admins(span_adminnotice("[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name]."))
log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].")
BLACKBOX_LOG_ADMIN_VERB("Ghost Drag Control")
tomob.PossessByPlayer(frommob.key)
tomob.client?.init_verbs()
qdel(frommob)
return TRUE
/// Sends a message to adminchat when anyone with a holder logs in or logs out.
/// Is dependent on admin preferences and configuration settings, which means that this proc can fire without sending a message.
/client/proc/adminGreet(logout = FALSE)
if(!SSticker.HasRoundStarted())
return
if(logout && CONFIG_GET(flag/announce_admin_logout))
message_admins("Admin logout: [key_name(src)]")
return
if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN))
message_admins("Admin login: [key_name(src)]")
return