From 0acab5b1961a1a8762bef581149f7679b56adcfa Mon Sep 17 00:00:00 2001 From: unid15 Date: Wed, 9 Aug 2017 15:12:48 +0200 Subject: [PATCH] Documentation & list copying update --- code/__HELPERS/_macros.dm | 2 + code/__HELPERS/typeof.dm | 53 +++--- code/datums/datumvars.dm | 21 ++- code/modules/admin/admin.dm | 2 +- code/modules/admin/verbs/debug.dm | 2 +- code/modules/admin/verbs/massmodvar.dm | 15 +- code/modules/admin/verbs/modifyvariables.dm | 190 ++++++++++---------- 7 files changed, 151 insertions(+), 134 deletions(-) diff --git a/code/__HELPERS/_macros.dm b/code/__HELPERS/_macros.dm index 0ff85a10267..b67c6e1293c 100644 --- a/code/__HELPERS/_macros.dm +++ b/code/__HELPERS/_macros.dm @@ -199,6 +199,8 @@ proc/get_space_area() //y is the minimum //z is the maximum +//Returns 1 if the variable contains a protected list that can't be edited +#define variable_contains_protected_list(var_name) (((var_name) == "contents") || ((var_name) == "locs") || ((var_name) == "vars")) #define CLAMP01(x) (Clamp(x, 0, 1)) diff --git a/code/__HELPERS/typeof.dm b/code/__HELPERS/typeof.dm index 3074a618194..c3daec38b61 100644 --- a/code/__HELPERS/typeof.dm +++ b/code/__HELPERS/typeof.dm @@ -1,60 +1,65 @@ -var/global/list/matching_type_list_cache = list() - -/proc/matching_type_list(object, parent_type = /atom) +//Returns a list of types derived from [parent_type], that contain the substring [partial_name] +//If the substring ends with a dot, only functions that END with the substring are returned +var/global/list/get_matching_types_cache = list() +/proc/get_matching_types(partial_name, parent_type = /atom) //Key string for the cache - var/key = "[object]:[parent_type]" + var/key = "[partial_name]:[parent_type]" //Get cached list's copy if it exists - var/list/cache = matching_type_list_cache[key] + var/list/cache = get_matching_types_cache[key] if(cache) return cache.Copy() var/list/matches = list() //The string is null or "" - no need for calculations - if(!object || !length(object)) + if(!partial_name || !length(partial_name)) return typesof(parent_type) - if(text_ends_with(object, ".")) //Path ends with a dot - DO NOT include subtypes - object = copytext(object, 1, length(object)) //Remove the dot + if(text_ends_with(partial_name, ".")) //Path ends with a dot - DO NOT include subtypes + partial_name = copytext(partial_name, 1, length(partial_name)) //Remove the dot for(var/path in typesof(parent_type)) - if(text_ends_with("[path]", object)) + if(text_ends_with("[path]", partial_name)) matches += path else //Include subtypes - for(var/path in typesof(/atom)) - if(findtext("[path]", object)) + for(var/path in typesof(parent_type)) + if(findtext("[path]", partial_name)) matches += path - matching_type_list_cache[key] = matches.Copy() + //Cache the result + get_matching_types_cache[key] = matches.Copy() return matches +//Returns a list of variables of an object of type [object_type]. +//Because it's impossible to access variables of a type, this proc creates a temporary datum, grabs its variables and deletes it, caching the result in a global list +//object_type CAN'T be a type of a turf (/turf) or an area (/area), because these datums can't be created in nullspace var/global/list/get_vars_from_type_cache = list() - -/proc/get_vars_from_type(T) - if(ispath(T, /atom) && !ispath(T, /atom/movable)) - //It's impossible to spawn a turf or an area without a location +/proc/get_vars_from_type(object_type) + if(ispath(object_type, /atom) && !ispath(object_type, /atom/movable)) //Attempting to proceed will result in a runtime error return null - var/list/cache = get_vars_from_type_cache[T] + var/list/cache = get_vars_from_type_cache[object_type] if(cache) return cache.Copy() var/list/variable_list = list() - var/datum/temp_datum = new T(null) + //Create a temporary datum in nullspace to access the variables + var/datum/temp_datum = new object_type(null) for(var/variable in temp_datum.vars) variable_list.Add(variable) - variable_list = sortList(variable_list) //Sort the variable list alphabetically - get_vars_from_type_cache[T] = variable_list + + //Sort the variable list alphabetically + variable_list = sortList(variable_list) + //Cache the result + get_vars_from_type_cache[object_type] = variable_list qdel(temp_datum) return variable_list -var/global/list/existing_typesof_cache = list() - //existing_typesof functions like typesof, with some differences //1) it only works with pathes derived from /atom //2) the returned list contains NO items without an icon state or an icon @@ -64,8 +69,8 @@ var/global/list/existing_typesof_cache = list() //resulting in an invisible monster. //Values are cached, so when doing existing_typesof(/atom), all paths derived from /atom will only be checked on the first call -//All calls afterwards will return a copy of a list from the cache - +//All calls with the same path afterwards will return a copy of a list from the cache +var/global/list/existing_typesof_cache = list() /proc/existing_typesof(var/path) if(!ispath(path, /atom)) return typesof(path) diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index 5146d14c33e..afe72fdd650 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -538,12 +538,27 @@ body return var/atom/A = locate(href_list["datumsave"]) + var/variable_name = href_list["varnamesave"] if(A) - var/saved_value = A.vars[href_list["varnamesave"]] + var/saved_value = A.vars[variable_name] - holder.marked_datum = saved_value - to_chat(usr, "Your marked datum is now: [holder.marked_datum]") + if(variable_contains_protected_list(variable_name)) //Checks for lists like 'vars', 'contents' and 'locs' that can't be edited + to_chat(usr, "The list [variable_name] is protected, and can't be saved. Saving a copy of it...") + var/list/L = saved_value + holder.marked_datum = L.Copy() + + else if(islist(saved_value)) + if(alert("Save this exact list, or a copy of it? A copy is independent, and changing it will not affect the original list.", "Datum saving", "Save Copy", "Save Exact") == "Save Copy") + var/list/L = saved_value + holder.marked_datum = L.Copy() + to_chat(usr, "Saved a copy of the [variable_name] list as your marked datum.") + else + holder.marked_datum = saved_value + to_chat(usr, "Saved the original [variable_name] list as your marked datum.") + else + holder.marked_datum = saved_value + to_chat(usr, "Your marked datum is now: [holder.marked_datum]") else if(href_list["mob_player_panel"]) if(!check_rights(0)) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 30536e539f3..53e66a46ec8 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1279,7 +1279,7 @@ var/global/floorIsLava = 0 if(!check_rights(R_SPAWN)) return - var/list/matches = matching_type_list(object, /atom) + var/list/matches = get_matching_types(object, /atom) if(matches.len==0) return diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 646805d6078..3daaf4be0c3 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -1210,7 +1210,7 @@ client/proc/check_convertables() if(!check_rights(R_SPAWN)) return - var/list/matches = matching_type_list(object, /datum) - typesof(/turf) + var/list/matches = get_matching_types(object, /datum) - typesof(/turf, /area) if(matches.len == 0) to_chat(usr, "Unable to find any matches.") diff --git a/code/modules/admin/verbs/massmodvar.dm b/code/modules/admin/verbs/massmodvar.dm index 835e0d850bc..c276a68cc0b 100644 --- a/code/modules/admin/verbs/massmodvar.dm +++ b/code/modules/admin/verbs/massmodvar.dm @@ -7,11 +7,12 @@ return if(istext(target_type)) - target_type = input("Select an object type to mass-modify", "Mass-editing") as null|anything in matching_type_list(target_type, /atom) + target_type = input("Select an object type to mass-modify", "Mass-editing") as null|anything in get_matching_types(target_type, /atom) //get_vars_from_type() fails on turf and area objects. If you want to mass-edit a turf, you have to do it through the varedit window if(ispath(target_type, /atom) && !ispath(target_type, /atom/movable)) to_chat(src, "It's impossible to perform this task on objects of type [target_type] through the verb. Use the mass edit function from View Variables instead.") + return if(!target_type) return @@ -40,15 +41,15 @@ if("Set new value") new_value = variable_set(usr) - mass_modify_variable(target_type, variable_name, new_value, reset_to_initial, include_subtypes) - - feedback_add_details("admin_verb","MEV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + //Log the action before actually performing it, in case it crashes the server + feedback_add_details("admin_verb","MEV") log_admin("[key_name(src)] mass modified [target_type]'s [variable_name] to [reset_to_initial ? "its initial value" : " [new_value] "]") message_admins("[key_name_admin(src)] mass modified [target_type]'s [variable_name] to [reset_to_initial ? "its initial value" : " [new_value] "]", 1) + mass_modify_variable(target_type, variable_name, new_value, reset_to_initial, include_subtypes) + //Mass-modifies all atoms of type [type], changing their variable [var_name] to [new_value] //If reset_to_initial is TRUE, the variables will be reset to their initial values, instead of getting a new value - /proc/mass_modify_variable(type, var_name, new_value, reset_to_initial = FALSE, include_subtypes = TRUE) var/base_path @@ -62,10 +63,10 @@ if(!ispath(base_path, /atom)) to_chat(usr, "Mass-editing is not supported for objects of type [base_path]") + return + //BYOND's internal optimisation makes this work better than cycling through every atom #define is_valid_atom(atom) (atom.type == base_path || (include_subtypes && istype(atom, base_path))) - - if(ispath(base_path, /turf)) for(var/turf/A in world) if(is_valid_atom(A)) diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index bdc79c93c15..69e7eabe0e0 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -5,16 +5,11 @@ var/list/forbidden_varedit_object_types = list( /datum/configuration, //prevents people from fucking with logging. ) -/* -/client/proc/cmd_modify_object_variables(obj/O as obj|mob|turf|area in world) - set category = "Debug" - set name = "Edit Variables" - set desc="(target) Edit a target item's variables" - src.modify_variables(O) - feedback_add_details("admin_verb","EDITV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -*/ - -/proc/variable_set(mob/user, datum/edited_datum, edited_variable, autoselect_var_type = FALSE) +//Interface for editing a variable. It doesn't change the variable - just returns its new value. +//If called with just [user] argument, it allows you to create a value such as a string, a number, an empty list, a nearby object, etc... +//If called with [edited_datum] and [edited_variable], you gain the ability to get the variable's initial value. +//In addition to that, if [autoselect_var_type] is TRUE, the proc will attempt to +/proc/variable_set(mob/user, datum/edited_datum = null, edited_variable = null, autoselect_var_type = FALSE) var/client/C if(ismob(user)) @@ -39,112 +34,111 @@ var/list/forbidden_varedit_object_types = list( #define V_CANCEL "cancel" #define V_MATRIX "matrix" - var/list/choices = list(\ - "text" = V_TEXT, - "num" = V_NUM, - "type" = V_TYPE, - "empty list" = V_LIST, - "object (nearby)" = V_OBJECT, - "icon" = V_ICON, - "file" = V_FILE, - "client" = V_CLIENT, - "matrix" = V_MATRIX, - "null" = V_NULL, - ) - - if(C.holder.marked_datum) - var/list_item_name - if(isdatum(C.holder.marked_datum)) - list_item_name = "marked datum ([C.holder.marked_datum.type])" - - else if(isfile(C.holder.marked_datum)) - list_item_name = "marked datum (file)" - - else if(isicon(C.holder.marked_datum)) - list_item_name = "marked datum (icon)" - - else - list_item_name = "marked datum ([C.holder.marked_datum])" - - choices[list_item_name] = V_MARKED_DATUM + var/new_variable_type + var/old_value = null //Old value of the variable + var/new_value //New value of the variable if(istype(edited_datum) && edited_variable) - choices["restore to default"] = V_RESET + old_value = edited_datum.vars[edited_variable] - //Cancel belongs at the end - choices["CANCEL"] = V_CANCEL + if(autoselect_var_type) + if(isnull(old_value)) + to_chat(usr, "Unable to determine variable type.") + else if(isnum(old_value)) + to_chat(usr, "Variable appears to be NUM.") + new_variable_type = V_NUM + else if(istext(old_value)) + to_chat(usr, "Variable appears to be TEXT.") + new_variable_type = V_TEXT + else if(isloc(old_value)) + to_chat(usr, "Variable appears to be REFERENCE. Selecting from nearby objects...") + new_variable_type = V_OBJECT + else if(isicon(old_value)) + to_chat(usr, "Variable appears to be ICON.") + new_variable_type = V_ICON + else if(ispath(old_value)) + to_chat(usr, "Variable appears to be TYPE.") + new_variable_type = V_TYPE + else if(istype(old_value,/client)) + to_chat(usr, "Variable appears to be CLIENT.") + new_variable_type = V_CLIENT + else if(isfile(old_value)) + to_chat(usr, "Variable appears to be FILE.") + new_variable_type = V_FILE + else if(islist(old_value)) + to_chat(usr, "Variable appears to be LIST.") + new_value = C.mod_list(old_value) //Use a custom interface for list editing + else if(ismatrix(old_value)) + to_chat(usr, "Variable appears to be MATRIX.") + new_value = C.modify_matrix_menu(old_value) //Use a custom interface for matrix editing - var/class - var/var_value = null //Old value of the variable - var/result //New value of the variable + if(!new_value) //If a custom interface hasn't already set the value - if(autoselect_var_type && istype(edited_datum) && edited_variable) - var_value = edited_datum.vars[edited_variable] + //Build the choices list + var/list/choices = list(\ + "text" = V_TEXT, + "num" = V_NUM, + "type" = V_TYPE, + "empty list" = V_LIST, + "object (nearby)" = V_OBJECT, + "icon" = V_ICON, + "file" = V_FILE, + "client" = V_CLIENT, + "matrix" = V_MATRIX, + "null" = V_NULL, + ) - if(isnull(var_value)) - to_chat(usr, "Unable to determine variable type.") - else if(isnum(var_value)) - to_chat(usr, "Variable appears to be NUM.") - class = V_NUM - else if(istext(var_value)) - to_chat(usr, "Variable appears to be TEXT.") - class = V_TEXT - else if(isloc(var_value)) - to_chat(usr, "Variable appears to be REFERENCE. Selecting from nearby objects...") - class = V_OBJECT - else if(isicon(var_value)) - to_chat(usr, "Variable appears to be ICON.") - class = V_ICON - else if(ispath(var_value)) - to_chat(usr, "Variable appears to be TYPE.") - class = V_TYPE - else if(istype(var_value,/client)) - to_chat(usr, "Variable appears to be CLIENT.") - class = V_CLIENT - else if(isfile(var_value)) - to_chat(usr, "Variable appears to be FILE.") - class = V_FILE - else if(islist(var_value)) - to_chat(usr, "Variable appears to be LIST.") - result = C.mod_list(var_value) - else if(ismatrix(var_value)) - to_chat(usr, "Variable appears to be MATRIX.") - result = C.modify_matrix_menu(var_value) + if(C.holder.marked_datum) //Add the marked datum option + var/list_item_name + if(isdatum(C.holder.marked_datum)) + list_item_name = "marked datum ([C.holder.marked_datum.type])" + else if(isfile(C.holder.marked_datum)) + list_item_name = "marked datum (file)" + else if(isicon(C.holder.marked_datum)) + list_item_name = "marked datum (icon)" + else + list_item_name = "marked datum ([C.holder.marked_datum])" + choices[list_item_name] = V_MARKED_DATUM + if(istype(edited_datum) && edited_variable) //Add the restore to default option + choices["restore to default"] = V_RESET - if(!class && !result) - class = input("What kind of variable?","Variable Type") in choices - var/selected_type = choices[class] + //Add the cancel option + choices["CANCEL"] = V_CANCEL + + if(!new_variable_type) + new_variable_type = input("What kind of variable?","Variable Type") in choices + var/selected_type = choices[new_variable_type] + var/window_title = "Varedit [edited_datum]" - if(!result) switch(selected_type) if(V_CANCEL) return if(V_TEXT) - result = input("Enter new text:","Text",null) as text + new_value = input("Enter new text:", window_title, old_value) as text if(V_NUM) - result = input("Enter new number:","Num",0) as num + new_value = input("Enter new number:", window_title, old_value) as num if(V_TYPE) - var/partial_type = input("Enter type, or leave blank to see all types", "Type") as text|null + var/partial_type = input("Enter type, or leave blank to see all types", window_title, "[old_value]") as text|null - var/list/matches = matching_type_list(partial_type, /datum) - result = input("Select type","Type") as null|anything in matches + var/list/matches = get_matching_types(partial_type, /datum) + new_value = input("Select type", window_title) as null|anything in matches if(V_LIST) - result = list() + new_value = list() if(V_OBJECT) - result = input("Select reference:","Reference",src) as mob|obj|turf|area in range(8, get_turf(user)) + new_value = input("Select reference:", window_title, old_value) as mob|obj|turf|area in range(8, get_turf(user)) if(V_FILE) - result = input("Pick file:","File") as file + new_value = input("Pick file:", window_title) as file if(V_ICON) - result = input("Pick icon:","Icon") as icon + new_value = input("Pick icon:", window_title) as icon if(V_CLIENT) var/list/keys = list() @@ -152,32 +146,32 @@ var/list/forbidden_varedit_object_types = list( if(M.client) keys += M.client - result = input("Please, select a player!", "Selection", null, null) as null|anything in keys + new_value = input("Please, select a player!", window_title, null, null) as null|anything in keys if(V_MARKED_DATUM) - result = C.holder.marked_datum + new_value = C.holder.marked_datum if(V_RESET) if(istype(edited_datum) && edited_variable) - result = initial(edited_datum.vars[edited_variable]) + new_value = initial(edited_datum.vars[edited_variable]) - edited_datum.vars[edited_variable] = result - to_chat(user, "Restored '[edited_variable]' to original value - [result]") + edited_datum.vars[edited_variable] = new_value + to_chat(user, "Restored '[edited_variable]' to original value - [new_value]") if(V_NULL) - result = null + new_value = null if(V_MATRIX) - result = matrix() + new_value = matrix() else to_chat(user, "Unknown type: [selected_type]") if(istype(edited_datum)) - if(edited_datum.variable_edited(edited_variable, var_value, result)) - return var_value //Return the old value if variable_edited blocked the edit + if(edited_datum.variable_edited(edited_variable, old_value, new_value)) + return old_value //Return the old value if the variable_edited proc blocked the edit - return result + return new_value #undef V_MARKED_DATUM #undef V_RESET