mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-16 04:34:21 +00:00
## About The Pull Request So, some admin verbs/tools that used tguis, i.e edit/debug planes, were available to admins with +DEBUG... but the ui_state used `GLOB.admin_state`, which checks for +ADMIN - meaning that if they _only_ had +DEBUG, they would have the verb... but it would do nothing when they used it. I've refactored `GLOB.admin_state`, `GLOB.debug_state`, and `GLOB.fun_state` into a merged `/datum/ui_state/admin_state`, with a var for which specific permissions are being checked for. You now use the `ADMIN_STATE(perms)` macro to get the UI state for those specific perms, i.e `admin_state(R_ADMIN)` or `admin_state(R_DEBUG)`, and the resulting UI state will check for _those specific perms_. These are initialized and cached in `GLOB.admin_states` (which should never be directly accessed). So, I've went thru every single usage of `GLOB.admin_state`, `GLOB.fun_state`, and `GLOB.debug_state`, and made them all use `ADMIN_STATE()` with the actual permission flags needed to use said UI in the first place. ## Why It's Good For The Game Kinda dumb for specific admin permissions to be granted verbs that don't let them use it anyways. ## Changelog 🆑 admin: Certain UI-based tools (plane debugger, filter editor, etc) that were given to admins with only +VAREDIT or +DEBUG, but refused to open without +ADMIN, now actually work for admins that have the needed permission. /🆑
292 lines
10 KiB
Plaintext
292 lines
10 KiB
Plaintext
#ifndef DISABLE_DREAMLUAU
|
|
/datum/lua_editor
|
|
var/datum/lua_state/current_state
|
|
|
|
/// Arguments for a function call or coroutine resume
|
|
var/list/arguments = list()
|
|
|
|
/// If not set, the global table will not be shown in the lua editor
|
|
var/show_global_table = FALSE
|
|
|
|
/// The log page we are currently on
|
|
var/page = 0
|
|
|
|
/// If set, we will force the editor's modal to be this
|
|
var/force_modal
|
|
|
|
/// If set, we will force the editor to look at this chunk
|
|
var/force_view_chunk
|
|
|
|
/// If set, we will force the script input to be this
|
|
var/force_input
|
|
|
|
/// If set, the latest code execution performed from the editor raised an error, and this is the message from that error
|
|
var/last_error
|
|
|
|
/datum/lua_editor/New(state, _quick_log_index)
|
|
. = ..()
|
|
if(state)
|
|
current_state = state
|
|
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
|
|
|
|
/datum/lua_editor/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "LuaEditor", "Lua")
|
|
ui.set_autoupdate(FALSE)
|
|
ui.open()
|
|
|
|
/datum/lua_editor/Destroy(force)
|
|
. = ..()
|
|
if(current_state)
|
|
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
|
|
|
|
/datum/lua_editor/ui_state(mob/user)
|
|
return ADMIN_STATE(R_DEBUG)
|
|
|
|
/datum/lua_editor/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["ss_lua_init"] = SSlua.initialized
|
|
if(!SSlua.initialized)
|
|
return data
|
|
|
|
data["noStateYet"] = !current_state
|
|
data["showGlobalTable"] = show_global_table
|
|
if(current_state)
|
|
if(current_state.log)
|
|
var/list/logs = current_state.log.Copy((page*50)+1, min((page+1)*50+1, current_state.log.len+1))
|
|
for(var/i in 1 to logs.len)
|
|
var/list/log = logs[i]
|
|
log = log.Copy()
|
|
if(log["return_values"])
|
|
log["return_values"] = kvpify_list(prepare_lua_editor_list(deep_copy_without_cycles(log["return_values"])))
|
|
logs[i] = log
|
|
data["stateLog"] = logs
|
|
data["page"] = page
|
|
data["pageCount"] = CEILING(current_state.log.len/50, 1)
|
|
data["tasks"] = current_state.get_tasks()
|
|
if(show_global_table)
|
|
current_state.get_globals()
|
|
var/list/values = current_state.globals["values"]
|
|
values = deep_copy_without_cycles(values)
|
|
values = prepare_lua_editor_list(values)
|
|
values = kvpify_list(values)
|
|
var/list/variants = current_state.globals["variants"]
|
|
data["globals"] = list("values" = values, "variants" = variants)
|
|
if(last_error)
|
|
data["lastError"] = last_error
|
|
last_error = null
|
|
data["supressRuntimes"] = current_state.supress_runtimes
|
|
data["states"] = list()
|
|
for(var/datum/lua_state/state as anything in SSlua.states)
|
|
data["states"] += state.display_name
|
|
data["callArguments"] = kvpify_list(prepare_lua_editor_list(deep_copy_without_cycles(arguments)))
|
|
if(force_modal)
|
|
data["forceModal"] = force_modal
|
|
force_modal = null
|
|
if(force_view_chunk)
|
|
data["forceViewChunk"] = force_view_chunk
|
|
force_view_chunk = null
|
|
if(force_input)
|
|
data["force_input"] = force_input
|
|
force_input = null
|
|
return data
|
|
|
|
/datum/lua_editor/proc/traverse_list(list/path, list/root, traversal_depth_offset = 0)
|
|
var/top_affected_list_depth = LAZYLEN(path)-traversal_depth_offset // The depth of the element to get
|
|
if(top_affected_list_depth)
|
|
var/list/current_list = root
|
|
// We kvpify the list to the depth of the element to get - this allows us to reach list elements contained within a assoc list's key
|
|
var/list/path_list = kvpify_list(current_list, top_affected_list_depth-1)
|
|
while(LAZYLEN(path) > traversal_depth_offset)
|
|
// Navigate to the index of the next path element within the current path element
|
|
var/list/path_element = popleft(path)
|
|
var/list/list_element = path_list[path_element["index"]]
|
|
|
|
// Enter the next path element - be it the key or the value
|
|
switch(path_element["type"])
|
|
if("key")
|
|
path_list = list_element["key"]
|
|
if("value")
|
|
path_list = list_element["value"]
|
|
else
|
|
to_chat(usr, span_warning("invalid path element type \[[path_element["type"]]] for list traversal (expected \"key\" or \"value\""))
|
|
return
|
|
// The element we are entering SHOULD be a list, unless we're at the end of the path
|
|
if(!islist(path_list) && LAZYLEN(path))
|
|
to_chat(usr, span_warning("invalid path element \[[path_list]] for list traversal (expected a list)"))
|
|
return
|
|
current_list = path_list
|
|
return current_list
|
|
else
|
|
return root
|
|
|
|
/datum/lua_editor/proc/run_code(code)
|
|
var/ckey = usr.ckey
|
|
current_state.ckey_last_runner = ckey
|
|
var/result = current_state.load_script(code)
|
|
var/index_with_result = current_state.log_result(result)
|
|
if(result["status"] == "error")
|
|
last_error = result["message"]
|
|
message_admins("[key_name(usr)] executed [length(code)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(current_state, index_with_result)]")
|
|
|
|
/datum/lua_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
var/mob/user = ui.user
|
|
if(!check_rights_for(user.client, R_DEBUG))
|
|
return
|
|
if(action == "runCodeFile")
|
|
params["code"] = file2text(input(user, "Input File") as null|file)
|
|
if(isnull(params["code"]))
|
|
return
|
|
action = "runCode"
|
|
switch(action)
|
|
if("newState")
|
|
var/state_name = params["name"]
|
|
if(!length(state_name))
|
|
return TRUE
|
|
var/datum/lua_state/new_state = new(state_name)
|
|
if(QDELETED(new_state))
|
|
return
|
|
SSlua.states += new_state
|
|
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
|
|
current_state = new_state
|
|
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
|
|
page = 0
|
|
return TRUE
|
|
if("switchState")
|
|
var/state_index = params["index"]
|
|
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
|
|
current_state = SSlua.states[state_index]
|
|
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
|
|
page = 0
|
|
return TRUE
|
|
if("runCode")
|
|
run_code(params["code"])
|
|
return TRUE
|
|
if("runFile")
|
|
var/code_file = input(user, "Select a script to run.", "Lua") as file|null
|
|
if(!code_file)
|
|
return TRUE
|
|
var/code = file2text(code_file)
|
|
run_code(code)
|
|
return TRUE
|
|
if("moveArgUp")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
|
|
var/index = popleft(path)["index"]
|
|
target_list.Swap(index-1, index)
|
|
return TRUE
|
|
if("moveArgDown")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
|
|
var/index = popleft(path)["index"]
|
|
target_list.Swap(index, index+1)
|
|
return TRUE
|
|
if("removeArg")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
|
|
var/index = popleft(path)["index"]
|
|
target_list.Cut(index, index+1)
|
|
return TRUE
|
|
if("addArg")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments)
|
|
if(target_list != arguments)
|
|
user?.client?.mod_list_add(target_list, null, "a lua editor", "arguments")
|
|
else
|
|
var/list/vv_val = user?.client?.vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
|
|
var/class = vv_val["class"]
|
|
if(!class)
|
|
return
|
|
LAZYADD(arguments, list(vv_val["value"]))
|
|
return TRUE
|
|
if("callFunction")
|
|
var/list/recursive_indices = params["indices"]
|
|
var/list/current_list = kvpify_list(current_state.globals["values"])
|
|
var/list/current_variants = current_state.globals["variants"]
|
|
var/function = list()
|
|
while(LAZYLEN(recursive_indices))
|
|
var/index = popleft(recursive_indices)
|
|
var/list/element = current_list[index]
|
|
var/key = element["key"]
|
|
var/value = element["value"]
|
|
var/list/variant_pair = current_variants[index]
|
|
var/key_variant = variant_pair["key"]
|
|
if(key_variant == "function" || key_variant == "thread" || key_variant == "userdata" || key_variant == "error_as_value")
|
|
to_chat(user, span_warning("invalid table key \[[key]] for function call (expected text, num, path, list, or ref, got [key_variant])"))
|
|
return
|
|
function += key
|
|
if(islist(value))
|
|
current_list = value
|
|
current_variants = variant_pair["value"]
|
|
else
|
|
if(variant_pair["value"] != "function")
|
|
to_chat(user, span_warning("invalid value \[[value]] for function call (expected list or function)"))
|
|
return
|
|
var/result = current_state.call_function(arglist(list(function) + arguments))
|
|
current_state.log_result(result)
|
|
if(result["status"] == "error")
|
|
last_error = result["message"]
|
|
arguments.Cut()
|
|
return
|
|
if("resumeTask")
|
|
var/task_index = params["index"]
|
|
SSlua.queue_resume(current_state, task_index, arguments)
|
|
arguments.Cut()
|
|
return TRUE
|
|
if("killTask")
|
|
var/is_sleep = params["is_sleep"]
|
|
var/index = params["index"]
|
|
SSlua.kill_task(current_state, is_sleep, index)
|
|
return TRUE
|
|
if("vvReturnValue")
|
|
var/log_entry_index = params["entryIndex"]
|
|
var/list/log_entry = current_state.log[log_entry_index]
|
|
var/thing_to_debug = traverse_list(params["indices"], log_entry["return_values"])
|
|
if(isweakref(thing_to_debug))
|
|
var/datum/weakref/ref = thing_to_debug
|
|
thing_to_debug = ref.resolve()
|
|
INVOKE_ASYNC(user.client, TYPE_PROC_REF(/client, debug_variables), thing_to_debug)
|
|
return FALSE
|
|
if("vvGlobal")
|
|
var/thing_to_debug = traverse_list(params["indices"], current_state.globals["values"])
|
|
if(isweakref(thing_to_debug))
|
|
var/datum/weakref/ref = thing_to_debug
|
|
thing_to_debug = ref.resolve()
|
|
INVOKE_ASYNC(user.client, TYPE_PROC_REF(/client, debug_variables), thing_to_debug)
|
|
return FALSE
|
|
if("clearArgs")
|
|
arguments.Cut()
|
|
return TRUE
|
|
if("toggleShowGlobalTable")
|
|
show_global_table = !show_global_table
|
|
return TRUE
|
|
if("toggleSupressRuntimes")
|
|
current_state.supress_runtimes = !current_state.supress_runtimes
|
|
return TRUE
|
|
if("nextPage")
|
|
page = min(page+1, CEILING(current_state.log.len/50, 1)-1)
|
|
return TRUE
|
|
if("previousPage")
|
|
page = max(page-1, 0)
|
|
return TRUE
|
|
if("nukeLog")
|
|
current_state.log.Cut()
|
|
return TRUE
|
|
|
|
/datum/lua_editor/ui_close(mob/user)
|
|
. = ..()
|
|
qdel(src)
|
|
#endif
|
|
|
|
ADMIN_VERB(lua_editor, R_DEBUG, "Open Lua Editor", "Its codin' time.", ADMIN_CATEGORY_DEBUG)
|
|
#ifndef DISABLE_DREAMLUAU
|
|
var/datum/lua_editor/editor = new
|
|
editor.ui_interact(user.mob)
|
|
#else
|
|
to_chat(user, span_warning("Lua support has been disabled at compile-time."), type = MESSAGE_TYPE_ADMINLOG, confidential = TRUE) // doing this instead of just disabling the verb entirely so it's clear WHY it doesn't work.
|
|
#endif
|