diff --git a/auxlua.dll b/auxlua.dll index a663d4a5817..7b29cc3d3d4 100755 Binary files a/auxlua.dll and b/auxlua.dll differ diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 7bc9d165ba8..40c12e6e0c7 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -813,8 +813,11 @@ return element /// Returns a copy of the list where any element that is a datum or the world is converted into a ref -/proc/refify_list(list/target_list) +/proc/refify_list(list/target_list, list/visited, path_accumulator = "list") + if(!visited) + visited = list() var/list/ret = list() + visited[target_list] = path_accumulator for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key @@ -830,7 +833,10 @@ else if(key == world) new_key = "world [REF(world)]" else if(islist(key)) - new_key = refify_list(key) + if(visited.Find(key)) + new_key = visited[key] + else + new_key = refify_list(key, visited, path_accumulator + "\[[i]\]") var/value if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) value = target_list[key] @@ -846,7 +852,10 @@ else if(value == world) value = "world [REF(world)]" else if(islist(value)) - value = refify_list(value) + if(visited.Find(value)) + value = visited[value] + else + value = refify_list(value, visited, path_accumulator + "\[[key]\]") var/list/to_add = list(new_key) if(value) to_add[new_key] = value @@ -859,18 +868,27 @@ * Converts a list into a list of assoc lists of the form ("key" = key, "value" = value) * so that list keys that are themselves lists can be fully json-encoded */ -/proc/kvpify_list(list/target_list, depth = INFINITY) +/proc/kvpify_list(list/target_list, depth = INFINITY, list/visited, path_accumulator = "list") + if(!visited) + visited = list() var/list/ret = list() + visited[target_list] = path_accumulator for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key if(islist(key) && depth) - new_key = kvpify_list(key, depth-1) + if(visited.Find(key)) + new_key = visited[key] + else + new_key = kvpify_list(key, depth-1, visited, path_accumulator + "\[[i]\]") var/value if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) value = target_list[key] if(islist(value) && depth) - value = kvpify_list(value, depth-1) + if(visited.Find(value)) + value = visited[value] + else + value = kvpify_list(value, depth-1, visited, path_accumulator + "\[[key]\]") if(value) ret += list(list("key" = new_key, "value" = value)) else @@ -909,22 +927,74 @@ return TRUE /// Returns a copy of the list where any element that is a datum is converted into a weakref -/proc/weakrefify_list(list/target_list) +/proc/weakrefify_list(list/target_list, list/visited, path_accumulator = "list") + if(!visited) + visited = list() var/list/ret = list() + visited[target_list] = path_accumulator for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key if(isdatum(key)) new_key = WEAKREF(key) else if(islist(key)) - new_key = weakrefify_list(key) + if(visited.Find(key)) + new_key = visited[key] + else + new_key = weakrefify_list(key, visited, path_accumulator + "\[[i]\]") var/value if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) value = target_list[key] if(isdatum(value)) value = WEAKREF(value) else if(islist(value)) - value = weakrefify_list(value) + if(visited.Find(value)) + value = visited[value] + else + value = weakrefify_list(value, visited, path_accumulator + "\[[key]\]") + var/list/to_add = list(new_key) + if(value) + to_add[new_key] = value + ret += to_add + if(i < target_list.len) + CHECK_TICK + return ret + +/// Returns a copy of a list where text values (except assoc-keys and string representations of lua-only values) are +/// wrapped in quotes and existing quote marks are escaped, +/// and nulls are replaced with the string "null" +/proc/encode_text_and_nulls(list/target_list, list/visited) + var/static/regex/lua_reference_regex + if(!lua_reference_regex) + lua_reference_regex = regex(@"^((function)|(table)|(thread)|(userdata)): 0x[0-9a-fA-F]+$") + if(!visited) + visited = list() + var/list/ret = list() + visited[target_list] = TRUE + for(var/i in 1 to target_list.len) + var/key = target_list[i] + var/new_key = key + if(istext(key) && !target_list[key] && !lua_reference_regex.Find(key)) + new_key = "\"[replacetext(key, "\"", "\\\"")]\"" + else if(islist(key)) + var/found_index = visited.Find(key) + if(found_index) + new_key = visited[found_index] + else + new_key = encode_text_and_nulls(key, visited) + else if(isnull(key)) + new_key = "null" + var/value + if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) + value = target_list[key] + if(istext(value) && !lua_reference_regex.Find(value)) + value = "\"[replacetext(value, "\"", "\\\"")]\"" + else if(islist(value)) + var/found_index = visited.Find(value) + if(found_index) + value = visited[found_index] + else + value = encode_text_and_nulls(value, visited) var/list/to_add = list(new_key) if(value) to_add[new_key] = value diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm index f5bc9911754..e046990b0ab 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm @@ -263,14 +263,6 @@ /proc/_turn(dir, angle) return turn(dir, angle) -/// For some reason, an atom's contents are a kind of list that auxtools can't work with as a list -/// This proc returns a copy of contents that is an ordinary list -/proc/_contents(atom/thing) - var/list/ret = list() - if(istype(thing)) - ret += thing.contents - return ret - /// Auxtools REALLY doesn't know how to handle filters as values; /// when passed as arguments to auxtools-called procs, they aren't simply treated as nulls - /// they don't even count towards the length of args. diff --git a/code/modules/admin/verbs/lua/README.md b/code/modules/admin/verbs/lua/README.md index e79fdd93695..c825c1e9cf5 100644 --- a/code/modules/admin/verbs/lua/README.md +++ b/code/modules/admin/verbs/lua/README.md @@ -3,57 +3,92 @@ --- ## Datums -DM datums are treated as Lua userdata, and can be stored in fields. Regular datums are referenced weakly, so if a datum has been deleted, the corresponding userdata will evaluate to `nil` when used in comparisons or functions. -Keep in mind that BYOND can't see that a datum is referenced in a Lua field, and will garbage collect it if it is not referenced anywhere in DM. +DM datums are treated as lua userdata, and can be stored in fields. Due to fundamental limitations in lua, userdata is inherently truthy. Since datum userdata can correspond to a deleted datum, which would evaluate to `null` in DM, the function [`datum:is_null()`](#datumisnull) is provided to offer a truthiness test consistent with DM. + +Keep in mind that BYOND can't see that a datum is referenced in a lua field, and will garbage collect it if it is not referenced anywhere in DM. ### datum:get_var(var) + Equivalent to DM's `datum.var` ### datum:set_var(var, value) + Equivalent to DM's `datum.var = value` ### datum:call_proc(procName, ...) + Equivalent to DM's `datum.procName(...)` +### datum:is_null() + +This function is used to evaluate the truthiness of a DM var. The lua statement `if datum:is_null() then` is equivalent to the DM statement `if(datum)`. + +### datum.vars + +Returns a userdatum that allows you to access and modifiy the vars of a DM datum by index. `datum.vars.foo` is equivalent to `datum:get_var("foo")`, while `datum.vars.foo = bar` is equivalent to `datum:set_var("foo", bar)` + --- ## Lists -In order to allow lists to be modified in-place across the DM-to-Lua language barrier, lists are treated as userdata. Whenever running code that expects a DM value, auxlua will attempt to convert tables into lists. -List references are subject to the same limitations as datum userdata, but you are less likely to encounter these limitations. +In order to allow lists to be modified in-place across the DM-to-lua language barrier, lists are treated as userdata. Whenever running code that expects a DM value, auxlua will attempt to convert tables into lists. + +List references are subject to the same limitations as datum userdata, but you are less likely to encounter these limitations for regular lists. + +Some lists (`vars`, `contents`, `overlays`, `underlays`, `vis_contents`, and `vis_locs`) are inherently attached to datums, and as such, their corresponding userdata contains a weak reference to the containing datum. Use [`list:is_null`](#listisnull) to validate these types of lists. ### list.len + Equivalent to DM's `list.len` ### list:get(index) + Equivalent to DM's `list[index]` ### list:set(index, value) + Equivalent to DM's `list[index] = value` ### list:add(value) + Equivalent to DM's `list.Add(value)` +### list:remove(value) + +Equivalent to DM's `list.Remove(value)` + ### list:to_table() + Converts a DM list into a lua table. ### list:of_type(type_path) + Will extract only values of type `type_path`. +### list:is_null() + +A similar truthiness test to [`datum:is_null()`](#datumisnull). This function only has the possibility of returning `false` for lists that are inherently attached to a datum (`vars`, `contents`, `overlays`, `underlays`, `vis_contents`, and `vis_locs`). + +### list.entries + +Returns a userdatum that allows you to access and modifiy the entries of the list by index. `list.entries.foo` is equivalent to `list:get("foo")`, while `list.entries.foo = bar` is equivalent to `list:set("foo", bar)` + --- ## The dm table + The `dm` table consists of the basic hooks into the DM language. ### dm.state_id -The address of the Lua state in memory. This is a copy of the internal value used by auxlua to locate the Lua state in a global hash map. + +The address of the lua state in memory. This is a copy of the internal value used by auxlua to locate the lua state in a global hash map. `state_id` is a registry value that is indirectly obtained using the `dm` table's `__index` metamethod. ### dm.global_proc(proc, ...) Calls the global proc `/proc/[proc]` with `...` as its arguments. ### dm.world -A reference to DM's `world`, in the form of datum userdata. This reference will never evaluate to `nil`, since `world` always exists. +A reference to DM's `world`, in the form of datum userdata. This reference is always valid, since `world` always exists. Due to limitations inherent in the wrapper functions used on tgstation, `world:set_var` and `world:call_proc` will raise an error. @@ -78,7 +113,7 @@ The Lua Scripting subsystem manages the execution of tasks for each Lua state. A ### sleep() Yields the current thread, scheduling it to be resumed during the next fire of SSlua. Use this function to prevent your Lua code from exceeding its allowed execution duration. Under the hood, `sleep` performs the following: -- Sets the global flag `__sleep_flag` +- Sets the [`sleep_flag`](#sleep_flag) - Calls `coroutine.yield()` - Clears the sleep flag when determining whether the task slept or yielded - Ignores the return values of `coroutine.yield()` once resumed @@ -156,29 +191,25 @@ end) --- ## Internal globals -Auxlua defines several globals for internal use. These are read-only. -### __sleep_flag -This flag is used to designate that a yielding task should be put in the sleep queue instead of the yield table. Once auxlua determines that a task should sleep, `__sleep_flag` is cleared. +Auxlua defines several registry values for each state. Note that there is no way to access registry values from lua code. -### __set_sleep_flag(value) +### sleep_flag -A function that sets `__sleep_flag` to `value`. Calling this directly is not recommended, as doing so muddies the distinction between sleeps and yields. +This flag is used to designate that a yielding task should be put in the sleep queue instead of the yield table. Once auxlua determines that a task should sleep, `sleep_flag` is cleared. -### __sleep_queue +### sleep_queue -A sequence of threads, each corresponding to a task that has slept. When calling `/proc/__lua_awaken`, auxlua will dequeue the first thread from the sequence and resume it. Threads in this queue can be resumed from Lua code, but doing so is heavily advised against. +A sequence of threads, each corresponding to a task that has slept. When calling `/proc/__lua_awaken`, auxlua will dequeue the first thread from the sequence and resume it. -### __yield_table +### yield_table -A table of threads, each corresponding to a coroutine that has yielded. When calling `/proc/__lua_resume`, auxlua will look for a thread at the index specified in the `index` argument, and resume it with the arguments specified in the `arguments` argument. Threads in this table can be resumed from Lua code, but doing so is heavily advised against. +A table of threads, each corresponding to a coroutine that has yielded. When calling `/proc/__lua_resume`, auxlua will look for a thread at the index specified in the `index` argument, and resume it with the arguments specified in the `arguments` argument. -### __task_info +### task_info A table of key-value-pairs, where the keys are threads, and the values are tables consisting of the following fields: - name: A string containing the name of the task - status: A string, either "sleep" or "yield" -- index: The task's index in `__sleep_queue` or `__yield_table` - -The threads constituting this table's keys can be resumed from Lua code, but doing so is heavily advised against. +- index: The task's index in `sleep_queue` or `yield_table` diff --git a/code/modules/admin/verbs/lua/lua_editor.dm b/code/modules/admin/verbs/lua/lua_editor.dm index 1b0e95749ba..0754f10902c 100644 --- a/code/modules/admin/verbs/lua/lua_editor.dm +++ b/code/modules/admin/verbs/lua/lua_editor.dm @@ -1,13 +1,10 @@ /datum/lua_editor var/datum/lua_state/current_state - /// Code imported from the user's system - var/imported_code - /// Arguments for a function call or coroutine resume var/list/arguments = list() - /// If set, the global table will not be shown in the lua editor + /// 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 @@ -125,7 +122,8 @@ if("runCode") var/code = params["code"] var/result = current_state.load_script(code) - current_state.log_result(result) + var/index_with_result = current_state.log_result(result) + message_admins("[key_name(usr)] executed [length(code)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(current_state, index_with_result)]") return TRUE if("moveArgUp") var/list/path = params["path"] diff --git a/code/modules/admin/verbs/lua/lua_state.dm b/code/modules/admin/verbs/lua/lua_state.dm index 0b836aac5e1..d7151e226ca 100644 --- a/code/modules/admin/verbs/lua/lua_state.dm +++ b/code/modules/admin/verbs/lua/lua_state.dm @@ -44,6 +44,7 @@ GLOBAL_PROTECT(lua_usr) && !(result["name"] == "input" && (result["status"] == "finished" || length(result["param"])))) return var/append_to_log = TRUE + var/index_of_log if(log.len) for(var/index in log.len to max(log.len - MAX_LOG_REPEAT_LOOKBACK, 1) step -1) var/list/entry = log[index] @@ -53,12 +54,17 @@ GLOBAL_PROTECT(lua_usr) && ((entry["param"] == result["param"]) || deep_compare_list(entry["param"], result["param"]))) if(!entry["repeats"]) entry["repeats"] = 0 + index_of_log = index entry["repeats"]++ append_to_log = FALSE break if(append_to_log) - log += list(weakrefify_list(result)) + if(islist(result["param"])) + result["param"] = weakrefify_list(encode_text_and_nulls(result["param"])) + log += list(result) + index_of_log = log.len INVOKE_ASYNC(src, /datum/lua_state.proc/update_editors) + return index_of_log /datum/lua_state/proc/load_script(script) GLOB.IsLuaCall = TRUE @@ -76,7 +82,6 @@ GLOBAL_PROTECT(lua_usr) result["chunk"] = script check_if_slept(result) - message_admins("[key_name(usr)] executed [length(script)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(src, log.len)]") log_lua("[key_name(usr)] executed the following lua code:\n[script]") return result @@ -137,7 +142,7 @@ GLOBAL_PROTECT(lua_usr) return result /datum/lua_state/proc/get_globals() - globals = weakrefify_list(__lua_get_globals(internal_id)) + globals = weakrefify_list(encode_text_and_nulls(__lua_get_globals(internal_id))) /datum/lua_state/proc/get_tasks() return __lua_get_tasks(internal_id) diff --git a/dependencies.sh b/dependencies.sh index 0f2e21aa3ad..c5b977c1a22 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -24,4 +24,4 @@ export PYTHON_VERSION=3.7.9 export AUXLUA_REPO=tgstation/auxlua #auxlua git tag -export AUXLUA_VERSION=1.0.0 +export AUXLUA_VERSION=1.1.1 diff --git a/lua/SS13.lua b/lua/SS13.lua index ea855d7228c..3eef19407e9 100644 --- a/lua/SS13.lua +++ b/lua/SS13.lua @@ -1,12 +1,11 @@ local SS13 = {} -SS13.SSlua = dm.global_vars:get_var("SSlua") +SS13.SSlua = dm.global_vars.vars.SSlua SS13.global_proc = "some_magic_bullshit" -local states = SS13.SSlua:get_var("states"):to_table() -for _, state in states do - if state:get_var("internal_id") == dm.state_id then +for _, state in SS13.SSlua.vars.states do + if state.vars.internal_id == dm.state_id then SS13.state = state break end @@ -18,7 +17,7 @@ end function SS13.new(type, ...) local datum = dm.global_proc("_new", type, { ... }) - local references = SS13.state:get_var("references") + local references = SS13.state.vars.references references:add(datum) return datum end @@ -31,18 +30,21 @@ function SS13.await(thing_to_call, proc_to_call, ...) proc_to_call = "/proc/" .. proc_to_call end local promise = SS13.new("/datum/auxtools_promise", thing_to_call, proc_to_call, ...) - while promise:get_var("status") == 0 do + local promise_vars = promise.vars + while promise_vars.status == 0 do sleep() end - return promise:get_var("return_value"), promise:get_var("runtime_message") + local return_value, runtime_message = promise_vars.return_value, promise_vars.runtime_message + dm.global_proc("qdel", promise) + return return_value, runtime_message end function SS13.wait(time, timer) - local index = #__yield_table + 1 - local callback = SS13.new("/datum/callback", SS13.SSlua, "queue_resume", SS13.state, index) + local callback = SS13.new("/datum/callback", SS13.SSlua, "queue_resume", SS13.state, __next_yield_index) local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, timer, debug.info(1, "sl")) coroutine.yield() dm.global_proc("deltimer", timedevent, timer) + dm.global_proc("qdel", callback) end function SS13.register_signal(datum, signal, func, make_easy_clear_function) @@ -52,26 +54,24 @@ function SS13.register_signal(datum, signal, func, make_easy_clear_function) if not SS13.istype(datum, "/datum") then return end - local ref = dm.global_proc("REF", datum) - if not SS13.signal_handlers[ref] then - SS13.signal_handlers[ref] = {} + if not SS13.signal_handlers[datum] then + SS13.signal_handlers[datum] = {} end if signal == "_cleanup" then return end - if not SS13.signal_handlers[ref][signal] then - SS13.signal_handlers[ref][signal] = {} + if not SS13.signal_handlers[datum][signal] then + SS13.signal_handlers[datum][signal] = {} end local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") callback:call_proc("RegisterSignal", datum, signal, "Invoke") - local callback_ref = dm.global_proc("REF", callback) - local path = { "SS13", "signal_handlers", ref, signal, callback_ref, "func" } - callback:set_var("arguments", { path }) - if not SS13.signal_handlers[ref]["_cleanup"] then - local cleanup_path = { "SS13", "signal_handlers", ref, "_cleanup", "func" } + local path = { "SS13", "signal_handlers", datum, signal, callback, "func" } + callback.vars.arguments = { path } + if not SS13.signal_handlers[datum]["_cleanup"] then + local cleanup_path = { "SS13", "signal_handlers", datum, "_cleanup", "func" } local cleanup_callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first", cleanup_path) cleanup_callback:call_proc("RegisterSignal", datum, "parent_qdeleting", "Invoke") - SS13.signal_handlers[ref]["_cleanup"] = { + SS13.signal_handlers[datum]["_cleanup"] = { func = function(datum) SS13.signal_handler_cleanup(datum) dm.global_proc("qdel", cleanup_callback) @@ -80,22 +80,21 @@ function SS13.register_signal(datum, signal, func, make_easy_clear_function) } end if signal == "parent_qdeleting" then --We want to make sure that the cleanup function is the very last signal handler called. - local comp_lookup = datum:get_var("comp_lookup") + local comp_lookup = datum.vars.comp_lookup if comp_lookup then - local lookup_table = comp_lookup:to_table() - local lookup_for_signal = lookup_table.parent_qdeleting + local lookup_for_signal = comp_lookup.entries.parent_qdeleting if lookup_for_signal and not SS13.istype(lookup_for_signal, "/datum") then local cleanup_callback_index = - dm.global_proc("_list_find", lookup_for_signal, SS13.signal_handlers[ref]["_cleanup"].callback) - if cleanup_callback_index ~= 0 then - dm.global_proc("_list_swap", lookup_for_signal, cleanup_callback_index, lookup_for_signal.len) + dm.global_proc("_list_find", lookup_for_signal, SS13.signal_handlers[datum]["_cleanup"].callback) + if cleanup_callback_index ~= 0 and cleanup_callback_index ~= #comp_lookup then + dm.global_proc("_list_swap", lookup_for_signal, cleanup_callback_index, #lookup_for_signal) end end end end - SS13.signal_handlers[ref][signal][callback_ref] = { func = func, callback = callback } + SS13.signal_handlers[datum][signal][callback] = { func = func, callback = callback } if make_easy_clear_function then - local clear_function_name = "clear_signal_" .. ref .. "_" .. signal .. "_" .. callback_ref + local clear_function_name = "clear_signal_" .. tostring(datum) .. "_" .. signal .. "_" .. tostring(callback) SS13[clear_function_name] = function() if callback then SS13.unregister_signal(datum, signal, callback) @@ -123,36 +122,34 @@ function SS13.unregister_signal(datum, signal, callback) return end - local ref = dm.global_proc("REF", datum) - local function clear_easy_clear_function(callback_ref) - local clear_function_name = "clear_signal_" .. ref .. "_" .. signal .. "_" .. callback_ref + local function clear_easy_clear_function(callback_to_clear) + local clear_function_name = "clear_signal_" .. tostring(datum) .. "_" .. signal .. "_" .. tostring(callback_to_clear) SS13[clear_function_name] = nil end - if not SS13.signal_handlers[ref] then + if not SS13.signal_handlers[datum] then return end if signal == "_cleanup" then return end - if not SS13.signal_handlers[ref][signal] then + if not SS13.signal_handlers[datum][signal] then return end if not callback then - for handler_key, handler_info in SS13.signal_handlers[ref][signal] do + for handler_key, handler_info in SS13.signal_handlers[datum][signal] do clear_easy_clear_function(handler_key) clear_handler(handler_info) end - SS13.signal_handlers[ref][signal] = nil + SS13.signal_handlers[datum][signal] = nil else if not SS13.istype(callback, "/datum/callback") then return end - local callback_ref = dm.global_proc("REF", callback) - clear_easy_clear_function(callback_ref) - clear_handler(SS13.signal_handlers[ref][signal][callback_ref]) - SS13.signal_handlers[ref][signal][callback_ref] = nil + clear_easy_clear_function(callback) + clear_handler(SS13.signal_handlers[datum][signal][callback]) + SS13.signal_handlers[datum][signal][callback] = nil end end @@ -160,31 +157,31 @@ function SS13.signal_handler_cleanup(datum) if not SS13.signal_handlers then return end - local ref = dm.global_proc("REF", datum) - if not SS13.signal_handlers[ref] then + if not SS13.signal_handlers[datum] then return end - for signal, _ in SS13.signal_handlers[ref] do + for signal, _ in SS13.signal_handlers[datum] do SS13.unregister_signal(datum, signal) end - SS13.signal_handlers[ref] = nil + SS13.signal_handlers[datum] = nil end -function SS13.set_timeout(time, func) +function SS13.set_timeout(time, func, timer) if not SS13.timeouts then SS13.timeouts = {} end local callback = SS13.new("/datum/callback", SS13.state, "call_function") - local callback_ref = dm.global_proc("REF", callback) - SS13.timeouts[callback_ref] = function() - SS13.timeouts[callback_ref] = nil + local timedevent = dm.global_proc("_addtimer", callback, time * 10, 8, timer, debug.info(1, "sl")) + SS13.timeouts[callback] = function() + SS13.timeouts[callback] = nil + dm.global_proc("deltimer", timedevent, timer) + dm.global_proc("qdel", callback) func() end - local path = { "SS13", "timeouts", callback_ref } - callback:set_var("arguments", { path }) - dm.global_proc("_addtimer", callback, time * 10, 8, nil, debug.info(1, "sl")) + local path = { "SS13", "timeouts", callback } + callback.vars.arguments = { path } end return SS13