diff --git a/.github/workflows/byond.yml b/.github/workflows/byond.yml index 323bbef4440..33d0948b55c 100644 --- a/.github/workflows/byond.yml +++ b/.github/workflows/byond.yml @@ -15,7 +15,7 @@ on: env: MACRO_COUNT: 0 GENDER_COUNT: 6 - TO_WORLD_COUNT: 187 + TO_WORLD_COUNT: 181 #These variables are filled from dependencies.sh inside the steps, DO NOT SET THEM HERE BYOND_MAJOR: "" diff --git a/aurorastation.dme b/aurorastation.dme index 82e15b4b985..baaa7e7947c 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -22,6 +22,7 @@ #include "code\__defines\_compile_helpers.dm" #include "code\__defines\_layers.dm" #include "code\__defines\_macros.dm" +#include "code\__defines\_protect.dm" #include "code\__defines\_unit_tests.dm" #include "code\__defines\_world.dm" #include "code\__defines\accessories.dm" @@ -149,6 +150,7 @@ #include "code\_helpers\matrices.dm" #include "code\_helpers\mobs.dm" #include "code\_helpers\mouse.dm" +#include "code\_helpers\nameof.dm" #include "code\_helpers\names.dm" #include "code\_helpers\overlay.dm" #include "code\_helpers\overmap.dm" @@ -1438,9 +1440,9 @@ #include "code\modules\admin\verbs\possess.dm" #include "code\modules\admin\verbs\pray.dm" #include "code\modules\admin\verbs\randomverbs.dm" -#include "code\modules\admin\verbs\SDQL.dm" #include "code\modules\admin\verbs\SDQL_2.dm" #include "code\modules\admin\verbs\SDQL_2_parser.dm" +#include "code\modules\admin\verbs\SDQL_2_wrappers.dm" #include "code\modules\admin\verbs\tripAI.dm" #include "code\modules\admin\verbs\viewlist.dm" #include "code\modules\admin\verbs\warning.dm" diff --git a/code/__defines/_macros.dm b/code/__defines/_macros.dm index 6ef3f282756..e68aebaea6b 100644 --- a/code/__defines/_macros.dm +++ b/code/__defines/_macros.dm @@ -22,6 +22,8 @@ #define SPAN_SOGHUN(X) ("" + X + "") #define SPAN_VOTE(X) ("" + X + "") #define SPAN_HEAR(X) ("" + X + "") +#define SPAN_STYLE(style, X) "[X]" +#define SPAN_COLOR(color, text) SPAN_STYLE("color: [color]", "[text]") #define SPAN_RED(x) "[x]" #define SPAN_YELLOW(x) "[x]" diff --git a/code/__defines/_protect.dm b/code/__defines/_protect.dm new file mode 100644 index 00000000000..e43d52ab76a --- /dev/null +++ b/code/__defines/_protect.dm @@ -0,0 +1,14 @@ +///Protects a datum from being VV'd or spawned through admin manipulation +#define GENERAL_PROTECT_DATUM(Path)\ +##Path/can_vv_get(var_name){\ + return FALSE;\ +}\ +##Path/vv_edit_var(var_name, var_value){\ + return FALSE;\ +}\ +##Path/Read(savefile/savefile){\ + qdel(src);\ +}\ +##Path/Write(savefile/savefile){\ + return;\ +} diff --git a/code/__defines/lists.dm b/code/__defines/lists.dm index 4dde5330033..b2ddc928ae0 100644 --- a/code/__defines/lists.dm +++ b/code/__defines/lists.dm @@ -16,6 +16,9 @@ #define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null #define LAZYREPLACEKEY(L, K, NK) if(L) { if(L[K]) { L[NK] = L[K] } else {L += NK} L -= K; } +/// Explicitly set the length of L to NEWLEN, adding nulls or dropping entries. Is the same value as NEWLEN. +#define LIST_RESIZE(L, NEWLEN) ((L).len = (NEWLEN)) + /// Performs an insertion on the given lazy list with the given key and value. If the value already exists, a new one will not be made. #define LAZYORASSOCLIST(lazy_list, key, value) \ LAZYINITLIST(lazy_list); \ diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 141b155bef8..2057a9bc2c7 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -269,13 +269,24 @@ //Large items are spawned on predetermined locations. //For each large spawn marker, this is the chance that we will spawn there - // Law settings #define PERMABRIG_SENTENCE 90 // Measured in minutes +/// for general usage of tick_usage +#define TICK_USAGE world.tick_usage +/// to be used where the result isn't checked +#define TICK_USAGE_REAL world.tick_usage + // Stoplag. -#define TICK_CHECK (world.tick_usage > CURRENT_TICKLIMIT) -#define CHECK_TICK if (TICK_CHECK) stoplag() +#define TICK_CHECK (TICK_USAGE > CURRENT_TICKLIMIT) +/// runs stoplag if tick_usage is above the limit +#define CHECK_TICK ( TICK_CHECK ? stoplag() : 0 ) + +/// Returns true if tick usage is above 95, for high priority usage +#define TICK_CHECK_HIGH_PRIORITY ( TICK_USAGE > 95 ) +/// runs stoplag if tick_usage is above 95, for high priority usage +#define CHECK_TICK_HIGH_PRIORITY ( TICK_CHECK_HIGH_PRIORITY ? stoplag() : 0 ) + // Performance bullshit. @@ -286,6 +297,12 @@ locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ ) +#define RECT_TURFS(H_RADIUS, V_RADIUS, CENTER) \ + block( \ + locate(max((CENTER).x-(H_RADIUS),1), max((CENTER).y-(V_RADIUS),1), (CENTER).z), \ + locate(min((CENTER).x+(H_RADIUS),world.maxx), min((CENTER).y+(V_RADIUS),world.maxy), (CENTER).z) \ + ) + #define get_turf(A) (get_step(A, 0)) #define NORTH_OF_TURF(T) locate(T.x, T.y + 1, T.z) #define EAST_OF_TURF(T) locate(T.x + 1, T.y, T.z) diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm index a873997afef..1e0be32afa2 100644 --- a/code/_helpers/maths.dm +++ b/code/_helpers/maths.dm @@ -1,7 +1,12 @@ +#define FLOOR(x) round(x) + +// round() acts like floor(x, 1) by default but can't handle other values +#define FLOOR_FLOAT(x, y) ( round((x) / (y)) * (y) ) + // min is inclusive, max is exclusive /proc/Wrap(val, min, max) var/d = max - min - var/t = Floor((val - min) / d) + var/t = FLOOR((val - min) / d) return val - (t * d) /proc/Default(a, b) @@ -25,17 +30,14 @@ var/a = arccos(x / sqrt(x*x + y*y)) return y >= 0 ? a : -a -/proc/Floor(x) - return round(x) - /// Value or the next integer in a positive direction: Ceil(-1.5) = -1 , Ceil(1.5) = 2 #define Ceil(value) ( -round(-(value)) ) /proc/Ceiling(x, y=1) return -round(-x / y) * y -/proc/Modulus(x, y) - return ( (x) - (y) * round((x) / (y)) ) +// Real modulus that handles decimals +#define MODULUS(x, y) ( (x) - FLOOR_FLOAT(x, y)) /proc/Percent(current_value, max_value, rounding = 1) return round((current_value / max_value) * 100, rounding) @@ -72,7 +74,7 @@ return (min < val && val < max) /proc/IsInteger(x) - return Floor(x) == x + return FLOOR(x) == x /proc/IsMultiple(x, y) return x % y == 0 @@ -163,11 +165,9 @@ if(isnum(num)&&isnum(min)&&isnum(max)) return ((min <= num) && (num <= max)) -#define MODULUS_FLOAT(X, Y) ( (X) - (Y) * round((X) / (Y)) ) - // Will filter out extra rotations and negative rotations // E.g: 540 becomes 180. -180 becomes 180. -#define SIMPLIFY_DEGREES(degrees) (MODULUS_FLOAT((degrees), 360)) +#define SIMPLIFY_DEGREES(degrees) (MODULUS((degrees), 360)) /// Value or the next multiple of divisor in a positive direction. Ceilm(-1.5, 0.3) = -1.5 , Ceilm(-1.5, 0.4) = -1.2 #define Ceilm(value, divisor) ( -round(-(value) / (divisor)) * (divisor) ) @@ -241,3 +241,4 @@ */ #define BEARING_RELATIVE(observer_x, observer_y, target_x, target_y) (90 - Atan2(target_x - observer_x, target_y - observer_y)) +#define ISINTEGER(x) (round(x) == x) diff --git a/code/_helpers/nameof.dm b/code/_helpers/nameof.dm new file mode 100644 index 00000000000..7cd5777f465 --- /dev/null +++ b/code/_helpers/nameof.dm @@ -0,0 +1,15 @@ +/** + * NAMEOF: Compile time checked variable name to string conversion + * evaluates to a string equal to "X", but compile errors if X isn't a var on datum. + * datum may be null, but it does need to be a typed var. + **/ +#define NAMEOF(datum, X) (#X || ##datum.##X) + +/** + * NAMEOF that actually works in static definitions because src::type requires src to be defined + */ +#if DM_VERSION >= 515 +#define NAMEOF_STATIC(datum, X) (nameof(type::##X)) +#else +#define NAMEOF_STATIC(datum, X) (#X || ##datum.##X) +#endif diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 7d847d8c172..e4b9400b606 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -72,3 +72,32 @@ var/real_round_start_time */ /proc/stop_watch(wh) return round(0.1 * (REALTIMEOFDAY - wh), 0.1) + +//Takes a value of time in deciseconds. +//Returns a text value of that number in hours, minutes, or seconds. +/proc/DisplayTimeText(time_value, round_seconds_to = 0.1) + var/second = FLOOR_FLOAT(time_value * 0.1, round_seconds_to) + if(!second) + return "right now" + if(second < 60) + return "[second] second[(second != 1)? "s":""]" + var/minute = FLOOR_FLOAT(second / 60, 1) + second = FLOOR_FLOAT(MODULUS(second, 60), round_seconds_to) + var/secondT + if(second) + secondT = " and [second] second[(second != 1)? "s":""]" + if(minute < 60) + return "[minute] minute[(minute != 1)? "s":""][secondT]" + var/hour = FLOOR_FLOAT(minute / 60, 1) + minute = MODULUS(minute, 60) + var/minuteT + if(minute) + minuteT = " and [minute] minute[(minute != 1)? "s":""]" + if(hour < 24) + return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" + var/day = FLOOR_FLOAT(hour / 24, 1) + hour = MODULUS(hour, 24) + var/hourT + if(hour) + hourT = " and [hour] hour[(hour != 1)? "s":""]" + return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index fd27cb0876a..5b21616d85b 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -478,6 +478,8 @@ var/list/gamemode_cache = list() var/asset_cdn_webroot = "" var/asset_cdn_url = null +GENERAL_PROTECT_DATUM(/datum/configuration) + /datum/configuration/New() var/list/L = typesof(/datum/game_mode) - /datum/game_mode for (var/T in L) diff --git a/code/controllers/subsystems/discord.dm b/code/controllers/subsystems/discord.dm index 9c8451ad433..fe8a84d6c85 100644 --- a/code/controllers/subsystems/discord.dm +++ b/code/controllers/subsystems/discord.dm @@ -34,6 +34,15 @@ SUBSYSTEM_DEF(discord) // with world.visibility == 0. var/alert_visibility = 0 +/datum/controller/subsystem/discord/can_vv_get(var_name) + if(var_name == NAMEOF(src, auth_token)) + return FALSE + return ..() + +/datum/controller/subsystem/discord/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, auth_token)) + return FALSE + return ..() /datum/controller/subsystem/discord/Initialize() config.load("config/discord.txt", "discord") diff --git a/code/controllers/subsystems/statistics.dm b/code/controllers/subsystems/statistics.dm index 7b40c9fbdf2..ea471b290f7 100644 --- a/code/controllers/subsystems/statistics.dm +++ b/code/controllers/subsystems/statistics.dm @@ -32,6 +32,8 @@ SUBSYSTEM_DEF(statistics) var/status_needs_update = FALSE +GENERAL_PROTECT_DATUM(/datum/controller/subsystem/statistics) + /datum/controller/subsystem/statistics/Initialize(timeofday) for (var/type in subtypesof(/datum/statistic) - list(/datum/statistic/numeric, /datum/statistic/grouped)) var/datum/statistic/S = new type diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index e9ea9b4a7a3..82645fcb40a 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -20,8 +20,10 @@ if (!Master.initializing && SS.flags & SS_NO_DISPLAY) continue available_controllers[SS.name] = SS - available_controllers["Evacuation Main Controller"] = evacuation_controller + available_controllers["Evacuation Controller"] = evacuation_controller var/css = input("What controller would you like to debug?", "Controllers") as null|anything in available_controllers + if(!css) + return debug_variables(available_controllers[css]) message_admins("Admin [key_name_admin(usr)] is debugging the [css] controller.") diff --git a/code/datums/beam.dm b/code/datums/beam.dm index a05c4859355..bc124cd9476 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -124,10 +124,10 @@ var/x_offset = round(sin(Angle) * (N + world.icon_size/2)) var/y_offset = round(cos(Angle) * (N + world.icon_size/2)) //Position the effect so the beam is one continuous line - segment.x += SIMPLE_SIGN(x_offset) * Floor(abs(x_offset)/world.icon_size) + segment.x += SIMPLE_SIGN(x_offset) * FLOOR(abs(x_offset)/world.icon_size) x_offset %= world.icon_size - segment.y += SIMPLE_SIGN(y_offset) * Floor(abs(y_offset)/world.icon_size) + segment.y += SIMPLE_SIGN(y_offset) * FLOOR(abs(y_offset)/world.icon_size) y_offset %= world.icon_size segment.pixel_x = x_offset diff --git a/code/datums/datum.dm b/code/datums/datum.dm index a66c0a3adcf..36f06e08a89 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -101,3 +101,14 @@ /datum/proc/process() set waitfor = FALSE return PROCESS_KILL + +/datum/proc/can_vv_get(var_name) + return TRUE + +/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited + if(var_name == NAMEOF(src, vars)) + return FALSE + if(!can_vv_get(var_name)) + return FALSE + vars[var_name] = var_value + return TRUE diff --git a/code/datums/feedback.dm b/code/datums/feedback.dm index a234872005a..69982753c86 100644 --- a/code/datums/feedback.dm +++ b/code/datums/feedback.dm @@ -3,6 +3,8 @@ var/value var/details +GENERAL_PROTECT_DATUM(/datum/feedback_variable) + /datum/feedback_variable/New(var/param_variable,var/param_value = 0) variable = param_variable value = param_value diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index baae16b1fc4..513ea651171 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -124,10 +124,10 @@ return new /datum/position(src) /datum/point/proc/return_px() - return Modulus(x, world.icon_size) - 16 - 1 + return MODULUS(x, world.icon_size) - 16 - 1 /datum/point/proc/return_py() - return Modulus(y, world.icon_size) - 16 - 1 + return MODULUS(y, world.icon_size) - 16 - 1 /datum/point/vector diff --git a/code/game/gamemodes/cult/narsie.dm b/code/game/gamemodes/cult/narsie.dm index ada56fab53e..8ac6bcbd8b3 100644 --- a/code/game/gamemodes/cult/narsie.dm +++ b/code/game/gamemodes/cult/narsie.dm @@ -1,6 +1,7 @@ var/global/narsie_behaviour = "CultStation13" var/global/narsie_cometh = 0 var/global/list/narsie_list = list() + /obj/singularity/narsie //Moving narsie to its own file for the sake of being clearer name = "Nar-Sie" desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees." diff --git a/code/game/gamemodes/events/holidays/holidays.dm b/code/game/gamemodes/events/holidays/holidays.dm index 496317d6843..39d8c06271e 100644 --- a/code/game/gamemodes/events/holidays/holidays.dm +++ b/code/game/gamemodes/events/holidays/holidays.dm @@ -153,7 +153,7 @@ var/global/Holiday = null //Allows GA and GM to set the Holiday variable /client/proc/Set_Holiday(T as text|null) - set name = ".Set Holiday" + set name = "Set Holiday" set category = "Fun" set desc = "Force-set the Holiday variable to make the game think it's a certain day." if(!check_rights(R_SERVER)) return diff --git a/code/game/machinery/floorlayer.dm b/code/game/machinery/floorlayer.dm index 74043fcf3c2..7d6c72b0533 100644 --- a/code/game/machinery/floorlayer.dm +++ b/code/game/machinery/floorlayer.dm @@ -19,13 +19,13 @@ if(on) if(mode["dismantle"]) - dismantleFloor(old_turf) + dismantle_floor(old_turf) if(mode["laying"]) - layFloor(old_turf) + lay_floor(old_turf) if(mode["collect"]) - CollectTiles(old_turf) + collect_tiles(old_turf) old_turf = new_turf @@ -44,7 +44,7 @@ if(istype(I, /obj/item/stack/tile)) to_chat(user, SPAN_NOTICE("You successfully load \the [I] into \the [src].")) user.drop_from_inventory(I, src) - TakeTile(I) + take_tile(I) return TRUE if(I.iscrowbar()) @@ -75,7 +75,7 @@ /obj/machinery/floorlayer/proc/reset() on = FALSE -/obj/machinery/floorlayer/proc/dismantleFloor(var/turf/new_turf) +/obj/machinery/floorlayer/proc/dismantle_floor(var/turf/new_turf) if(istype(new_turf, /turf/simulated/floor)) var/turf/simulated/floor/T = new_turf if(!T.is_plating()) @@ -85,13 +85,13 @@ return T.is_plating() return FALSE -/obj/machinery/floorlayer/proc/TakeNewStack() +/obj/machinery/floorlayer/proc/take_new_stack() for(var/obj/item/stack/tile/tile in contents) T = tile return TRUE return FALSE -/obj/machinery/floorlayer/proc/SortStacks() +/obj/machinery/floorlayer/proc/sort_stacks() for(var/obj/item/stack/tile/tile1 in contents) if (tile1 && tile1.get_amount() > 0) if (!T || T.type == tile1.type) @@ -101,20 +101,20 @@ if (tile2 != tile1 && tile2.type == tile1.type) tile2.transfer_to(tile1) -/obj/machinery/floorlayer/proc/layFloor(var/turf/w_turf) +/obj/machinery/floorlayer/proc/lay_floor(var/turf/w_turf) if(!T) - if(!TakeNewStack()) + if(!take_new_stack()) return FALSE w_turf.attackby(T, src) return TRUE -/obj/machinery/floorlayer/proc/TakeTile(var/obj/item/stack/tile/tile) +/obj/machinery/floorlayer/proc/take_tile(var/obj/item/stack/tile/tile) if(!T) T = tile tile.forceMove(src) - SortStacks() + sort_stacks() -/obj/machinery/floorlayer/proc/CollectTiles(var/turf/w_turf) +/obj/machinery/floorlayer/proc/collect_tiles(var/turf/w_turf) for(var/obj/item/stack/tile/tile in w_turf) - TakeTile(tile) + take_tile(tile) diff --git a/code/game/objects/items/tajara.dm b/code/game/objects/items/tajara.dm index 26d6969cba6..dc4020384ba 100644 --- a/code/game/objects/items/tajara.dm +++ b/code/game/objects/items/tajara.dm @@ -168,7 +168,7 @@ var/adhomian_time = real_time if(ISEVEN(current_day)) adhomian_time = real_time + 24 - adhomian_day = Floor(current_day / 2) + adhomian_day = FLOOR(current_day / 2) to_chat(usr, "You check your [src.name], glancing over at the watch face, reading the time to be '[adhomian_time]'. Today's date is the '[adhomian_day]th day of [adhomian_month], [adhomian_year]'.") diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 5503b78e488..b1b0d69692c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -203,7 +203,6 @@ var/list/admin_verbs_debug = list( /client/proc/callproc, /client/proc/callproc_target, /client/proc/toggledebuglogs, - /client/proc/SDQL_query, /client/proc/SDQL2_query, /client/proc/Jump, /client/proc/jumptomob, @@ -384,7 +383,6 @@ var/list/admin_verbs_hideable = list( /client/proc/delete_random_map, /client/proc/enable_debug_verbs, /client/proc/fix_player_list, - /client/proc/SDQL_query, /client/proc/SDQL2_query, /client/proc/cmd_admin_dress, /client/proc/kill_air, diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index a8d7f45ca9c..8efce31e4e0 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -18,6 +18,15 @@ var/list/admin_datums = list() var/list/watched_processes // Processes marked to be shown in Status instead of just Processes. +/datum/admins/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, rights)) + return FALSE + if(var_name == NAMEOF(src, owner)) + return FALSE + if(var_name == NAMEOF(src, original_mob)) + return FALSE + return ..() + /datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey) if(!ckey) log_world("ERROR: Admin datum created without a ckey argument. Datum has been deleted") diff --git a/code/modules/admin/verbs/SDQL.dm b/code/modules/admin/verbs/SDQL.dm deleted file mode 100644 index 67c56d6cee3..00000000000 --- a/code/modules/admin/verbs/SDQL.dm +++ /dev/null @@ -1,497 +0,0 @@ - -//Structured Datum Query Language. Basically SQL meets BYOND objects. - -//Note: For use in BS12, need text_starts_with proc, and to modify the action on select to use BS12's object edit command(s). - -/client/proc/SDQL_query(query_text as message) - set category = "Admin" - if(!check_rights(R_DEBUG)) //Shouldn't happen... but just to be safe. - message_admins("ERROR: Non-admin [usr.key] attempted to execute a SDQL query!") - log_admin("Non-admin [usr.key] attempted to execute a SDQL query!",level=2,ckey=key_name(usr)) - - var/list/query_list = SDQL_tokenize(query_text) - - if(query_list.len < 2) - if(query_list.len > 0) - to_chat(usr, "SDQL: Too few discrete tokens in query \"[query_text]\". Please check your syntax and try again.") - return - - if(!(lowertext(query_list[1]) in list("select", "delete", "update"))) - to_chat(usr, "SDQL: Unknown query type: \"[query_list[1]]\" in query \"[query_text]\". Please check your syntax and try again.") - return - - var/list/types = list() - - var/i - for(i = 2; i <= query_list.len; i += 2) - types += query_list[i] - - if(i + 1 >= query_list.len || query_list[i + 1] != ",") - break - - i++ - - var/list/from = list() - - if(i <= query_list.len) - if(lowertext(query_list[i]) in list("from", "in")) - for(i++; i <= query_list.len; i += 2) - from += query_list[i] - - if(i + 1 >= query_list.len || query_list[i + 1] != ",") - break - - i++ - - if(from.len < 1) - from += "world" - - var/list/set_vars = list() - - if(lowertext(query_list[1]) == "update") - if(i <= query_list.len && lowertext(query_list[i]) == "set") - for(i++; i <= query_list.len; i++) - if(i + 2 <= query_list.len && query_list[i + 1] == "=") - set_vars += query_list[i] - set_vars[query_list[i]] = query_list[i + 2] - - else - to_chat(usr, "SDQL: Invalid set parameter in query \"[query_text]\". Please check your syntax and try again.") - return - - i += 3 - - if(i >= query_list.len || query_list[i] != ",") - break - - if(set_vars.len < 1) - to_chat(usr, "SDQL: Invalid or missing set in query \"[query_text]\". Please check your syntax and try again.") - return - - var/list/where = list() - - if(i <= query_list.len && lowertext(query_list[i]) == "where") - where = query_list.Copy(i + 1) - - var/list/from_objs = list() - if("world" in from) - from_objs += world - else - for(var/f in from) - if(copytext(f, 1, 2) == "'" || copytext(f, 1, 2) == "\"") - from_objs += locate(copytext(f, 2, length(f))) - else if(copytext(f, 1, 2) != "/") - from_objs += locate(f) - else - var/f2 = text2path(f) - if(text_starts_with(f, "/mob")) - for(var/mob/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/turf/space")) - for(var/turf/space/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/turf/simulated")) - for(var/turf/simulated/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/turf/unsimulated")) - for(var/turf/unsimulated/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/turf")) - for(var/turf/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/area")) - for(var/area/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/obj/item")) - for(var/obj/item/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/obj/machinery")) - for(var/obj/machinery/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/obj")) - for(var/obj/m in world) - if(istype(m, f2)) - from_objs += m - - else if(text_starts_with(f, "/atom")) - for(var/atom/m in world) - if(istype(m, f2)) - from_objs += m -/* - else - for(var/datum/m in world) - if(istype(m, f2)) - from_objs += m -*/ - - var/list/objs = list() - - for(var/from_obj in from_objs) - if("*" in types) - objs += from_obj:contents - else - for(var/f in types) - if(copytext(f, 1, 2) == "'" || copytext(f, 1, 2) == "\"") - objs += locate(copytext(f, 2, length(f))) in from_obj - else if(copytext(f, 1, 2) != "/") - objs += locate(f) in from_obj - else - var/f2 = text2path(f) - if(text_starts_with(f, "/mob")) - for(var/mob/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/turf/space")) - for(var/turf/space/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/turf/simulated")) - for(var/turf/simulated/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/turf/unsimulated")) - for(var/turf/unsimulated/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/turf")) - for(var/turf/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/area")) - for(var/area/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/obj/item")) - for(var/obj/item/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/obj/machinery")) - for(var/obj/machinery/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/obj")) - for(var/obj/m in from_obj) - if(istype(m, f2)) - objs += m - - else if(text_starts_with(f, "/atom")) - for(var/atom/m in from_obj) - if(istype(m, f2)) - objs += m - - else - for(var/datum/m in from_obj) - if(istype(m, f2)) - objs += m - - - for(var/datum/t in objs) - var/currently_false = 0 - for(i = 1, i - 1 < where.len, i++) - var/v = where[i++] - var/compare_op = where[i++] - if(!(compare_op in list("==", "=", "<>", "<", ">", "<=", ">=", "!="))) - to_chat(usr, "SDQL: Unknown comparison operator [compare_op] in where clause following [v] in query \"[query_text]\". Please check your syntax and try again.") - return - - var/j - for(j = i, j <= where.len, j++) - if(lowertext(where[j]) in list("and", "or", ";")) - break - - if(!currently_false) - var/value = SDQL_text2value(t, v) - var/result = SDQL_evaluate(t, where.Copy(i, j)) - - switch(compare_op) - if("=", "==") - currently_false = !(value == result) - - if("!=", "<>") - currently_false = !(value != result) - - if("<") - currently_false = !(value < result) - - if(">") - currently_false = !(value > result) - - if("<=") - currently_false = !(value <= result) - - if(">=") - currently_false = !(value >= result) - - - if(j > where.len || lowertext(where[j]) == ";") - break - else if(lowertext(where[j]) == "or") - if(currently_false) - currently_false = 0 - else - break - - i = j - - if(currently_false) - objs -= t - - - - to_chat(usr, "SQDL Query: [query_text]") - message_admins("[usr] executed SDQL query: \"[query_text]\".") -/* - for(var/t in types) - to_chat(usr, "Type: [t]") - - for(var/t in from) - to_chat(usr, "From: [t]") - - for(var/t in set_vars) - to_chat(usr, "Set: [t] = [set_vars[t]]") - - if(where.len) - var/where_str = "" - for(var/t in where) - where_str += "[t] " - - to_chat(usr, "Where: [where_str]") - - to_chat(usr, "From objects:") - for(var/datum/t in from_objs) - to_chat(usr, t) - - to_chat(usr, "Objects:") - for(var/datum/t in objs) - to_chat(usr, t) -*/ - switch(lowertext(query_list[1])) - if("delete") - for(var/datum/t in objs) - qdel(t) - - if("update") - for(var/datum/t in objs) - objs[t] = list() - for(var/v in set_vars) - if(v in t.vars) - objs[t][v] = SDQL_text2value(t, set_vars[v]) - - for(var/datum/t in objs) - for(var/v in objs[t]) - t.vars[v] = objs[t][v] - - if("select") - var/text = "" - for(var/datum/t in objs) - if(istype(t, /atom)) - var/atom/a = t - - if(a.x) - text += "\ref[t]: [t] at ([a.x], [a.y], [a.z])
" - - else if(a.loc && a.loc.x) - text += "\ref[t]: [t] in [a.loc] at ([a.loc.x], [a.loc.y], [a.loc.z])
" - - else - text += "\ref[t]: [t]
" - - else - text += "\ref[t]: [t]
" - - //text += "[t]
" - usr << browse(text, "window=sdql_result") - - -/client/Topic(href,href_list[],hsrc) - if(href_list["SDQL_select"]) - debug_variables(locate(href_list["SDQL_select"])) - - ..() - - -/proc/SDQL_evaluate(datum/object, list/equation) - if(equation.len == 0) - return null - - else if(equation.len == 1) - return SDQL_text2value(object, equation[1]) - - else if(equation[1] == "!") - return !SDQL_evaluate(object, equation.Copy(2)) - - else if(equation[1] == "-") - return -SDQL_evaluate(object, equation.Copy(2)) - - - else - to_chat(usr, "SDQL: Sorry, equations not yet supported :(") - return null - - -/proc/SDQL_text2value(datum/object, text) - if(text2num(text) != null) - return text2num(text) - else if(text == "null") - return null - else if(copytext(text, 1, 2) == "'" || copytext(text, 1, 2) == "\"" ) - return copytext(text, 2, length(text)) - else if(copytext(text, 1, 2) == "/") - return text2path(text) - else - if(findtext(text, ".")) - var/split = findtext(text, ".") - var/v = copytext(text, 1, split) - - if((v in object.vars) && istype(object.vars[v], /datum)) - return SDQL_text2value(object.vars[v], copytext(text, split + 1)) - else - return null - - else - if(text in object.vars) - return object.vars[text] - else - return null - - -/proc/text_starts_with(text, start) - if(copytext(text, 1, length(start) + 1) == start) - return 1 - else - return 0 - - - - - -/proc/SDQL_tokenize(query_text) - - var/list/whitespace = list(" ", "\n", "\t") - var/list/single = list("(", ")", ",", "+", "-") - var/list/multi = list( - "=" = list("", "="), - "<" = list("", "=", ">"), - ">" = list("", "="), - "!" = list("", "=")) - - var/word = "" - var/list/query_list = list() - var/len = length(query_text) - - for(var/i = 1, i <= len, i++) - var/char = copytext(query_text, i, i + 1) - - if(char in whitespace) - if(word != "") - query_list += word - word = "" - - else if(char in single) - if(word != "") - query_list += word - word = "" - - query_list += char - - else if(char in multi) - if(word != "") - query_list += word - word = "" - - var/char2 = copytext(query_text, i + 1, i + 2) - - if(char2 in multi[char]) - query_list += "[char][char2]" - i++ - - else - query_list += char - - else if(char == "'") - if(word != "") - to_chat(usr, "SDQL: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.") - return null - - word = "'" - - for(i++, i <= len, i++) - char = copytext(query_text, i, i + 1) - - if(char == "'") - if(copytext(query_text, i + 1, i + 2) == "'") - word += "'" - i++ - - else - break - - else - word += char - - if(i > len) - to_chat(usr, "SDQL: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again.") - return null - - query_list += "[word]'" - word = "" - - else if(char == "\"") - if(word != "") - to_chat(usr, "SDQL: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.") - return null - - word = "\"" - - for(i++, i <= len, i++) - char = copytext(query_text, i, i + 1) - - if(char == "\"") - if(copytext(query_text, i + 1, i + 2) == "'") - word += "\"" - i++ - - else - break - - else - word += char - - if(i > len) - to_chat(usr, "SDQL: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again.") - return null - - query_list += "[word]\"" - word = "" - - else - word += char - - if(word != "") - query_list += word - - return query_list diff --git a/code/modules/admin/verbs/SDQL_2.dm b/code/modules/admin/verbs/SDQL_2.dm index 571129ced6a..061b6007215 100644 --- a/code/modules/admin/verbs/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL_2.dm @@ -1,218 +1,803 @@ +//SDQL2 datumized, /tg/station special! +/* + Welcome admins, badmins and coders alike, to Structured Datum Query Language. + SDQL allows you to powerfully run code on batches of objects (or single objects, it's still unmatched + even there.) + When I say "powerfully" I mean it you're in for a ride. + + Ok so say you want to get a list of every mob. How does one do this? + "SELECT /mob" + This will open a list of every object in world that is a /mob. + And you can VV them if you need. + + What if you want to get every mob on a *specific z-level*? + "SELECT /mob WHERE z == 4" + + What if you want to select every mob on even numbered z-levels? + "SELECT /mob WHERE z % 2 == 0" + + Can you see where this is going? You can select objects with an arbitrary expression. + These expressions can also do variable access and proc calls (yes, both on-object and globals!) + Keep reading! + + Ok. What if you want to get every machine in the SSmachine process list? Looping through world is kinda + slow. + + "SELECT * IN SSmachinery.machinery" + + Here "*" as type functions as a wildcard. + We know everything in the global SSmachinery.machinery list is a machine. + + You can specify "IN " to return a list to operate on. + This can be any list that you can wizard together from global variables and global proc calls. + Every variable/proc name in the "IN" block is global. + It can also be a single object, in which case the object is wrapped in a list for you. + So yeah SDQL is unironically better than VV for complex single-object operations. + + You can of course combine these. + "SELECT * IN SSmachinery.machinery WHERE z == 4" + "SELECT * IN SSmachinery.machinery WHERE stat & 2" // (2 is NOPOWER, can't use defines from SDQL. Sorry!) + "SELECT * IN SSmachinery.machinery WHERE stat & 2 && z == 4" + + The possibilities are endless (just don't crash the server, ok?). + + Oh it gets better. + + You can use "MAP " to run some code per object and use the result. For example: + + "SELECT /obj/machinery/power/smes MAP [charge / capacity * 100, RCon_tag, src]" + + This will give you a list of all the APCs, their charge AND RCon tag. Useful eh? + + [] being a list here. Yeah you can write out lists directly without > lol lists in VV. Color matrix + shenanigans inbound. + + After the "MAP" segment is executed, the rest of the query executes as if it's THAT object you just made + (here the list). + Yeah, by the way, you can chain these MAP / WHERE things FOREVER! + + "SELECT /mob WHERE client MAP client WHERE holder MAP holder" + + You can also generate a new list on the fly using a selector array. @[] will generate a list of objects based off the selector provided. + + "SELECT /mob/living IN (@[/area/service/bar MAP contents])[1]" + + What if some dumbass admin spawned a bajillion spiders and you need to kill them all? + Oh yeah you'd rather not delete all the spiders in maintenace. Only that one room the spiders were + spawned in. + + "DELETE /mob/living/simple_animal/hostile/giant_spider WHERE loc.loc == marked" + + Here I used VV to mark the area they were in, and since loc.loc = area, voila. + Only the spiders in a specific area are gone. + + Or you know if you want to catch spiders that crawled into lockers too (how even?) + + "DELETE /mob/living/carbon/hostile/giant_spider WHERE global.get_area(src) == marked" + + What else can you do? + + Well suppose you'd rather gib those spiders instead of simply flat deleting them... + + "CALL gib() ON /mob/living/carbon/hostile/giant_spider WHERE global.get_area(src) == marked" + + Or you can have some fun.. + + "CALL forceMove(marked) ON /mob/living/carbon/hostile" + + You can also run multiple queries sequentially: + + "CALL forceMove(marked) ON /mob/living/carbon/hostile; CALL gib() ON + /mob/living/carbon/hostile" + + And finally, you can directly modify variables on objects. + + "UPDATE /mob WHERE client SET client.color = [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]" + + Don't crash the server, OK? + + "UPDATE /mob/living/carbon/human/species/monkey SET #null = forceMove(usr.loc)" + + Writing "#null" in front of the "=" will call the proc and discard the return value. + + A quick recommendation: before you run something like a DELETE or another query.. Run it through SELECT + first. + You'd rather not gib every player on accident. + Or crash the server. + + By the way, queries are slow and take a while. Be patient. + They don't hang the entire server though. + + With great power comes great responsability. + + Here's a slightly more formal quick reference. + + The 4 queries you can do are: + + "SELECT " + "CALL ON " + "UPDATE SET var=,var2=" + "DELETE " + + "" in this context is " [IN ] [chain of MAP/WHERE modifiers]" + + "IN" (or "FROM", that works too but it's kinda weird to read), + is the list of objects to work on. This defaults to world if not provided. + But doing something like "IN living_mob_list" is quite handy and can optimize your query. + All names inside the IN block are global scope, so you can do living_mob_list (a global var) easily. + You can also run it on a single object. Because SDQL is that convenient even for single operations. + + filters out objects of, well, that type easily. "*" is a wildcard and just takes everything in + the source list. + + And then there's the MAP/WHERE chain. + These operate on each individual object being ran through the query. + They're both expressions like IN, but unlike it the expression is scoped *on the object*. + So if you do "WHERE z == 4", this does "src.z", effectively. + If you want to access global variables, you can do `global.living_mob_list`. + Same goes for procs. + + MAP "changes" the object into the result of the expression. + WHERE "drops" the object if the expression is falsey (0, null or "") + + What can you do inside expressions? + + * Proc calls + * Variable reads + * Literals (numbers, strings, type paths, etc...) + * \ref referencing: {0x30000cc} grabs the object with \ref [0x30000cc] + * Lists: [a, b, c] or [a: b, c: d] + * Math and stuff. + * A few special variables: src (the object currently scoped on), usr (your mob), + marked (your marked datum), global(global scope) + + TG ADDITIONS START: + Add USING keyword to the front of the query to use options system + The defaults aren't necessarily implemented, as there is no need to. + Available options: (D) means default + PROCCALL = (D)ASYNC, BLOCKING + SELECT = FORCE_NULLS, (D)SKIP_NULLS + PRIORITY = HIGH, (D) NORMAL + AUTOGC = (D) AUTOGC, KEEP_ALIVE + SEQUENTIAL = TRUE - The queries in this batch will be executed sequentially one by one not in parallel + + Example: USING PROCCALL = BLOCKING, SELECT = FORCE_NULLS, PRIORITY = HIGH SELECT /mob FROM world WHERE z == 1 + +*/ + + +#define SDQL2_STATE_ERROR 0 +#define SDQL2_STATE_IDLE 1 +#define SDQL2_STATE_PRESEARCH 2 +#define SDQL2_STATE_SEARCHING 3 +#define SDQL2_STATE_EXECUTING 4 +#define SDQL2_STATE_SWITCHING 5 +#define SDQL2_STATE_HALTING 6 + +#define SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS (1<<0) +#define SDQL2_OPTION_BLOCKING_CALLS (1<<1) +#define SDQL2_OPTION_HIGH_PRIORITY (1<<2) //High priority SDQL query, allow using almost all of the tick. +#define SDQL2_OPTION_DO_NOT_AUTOGC (1<<3) +#define SDQL2_OPTION_SEQUENTIAL (1<<4) + +#define SDQL2_OPTIONS_DEFAULT (SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + +#define SDQL2_IS_RUNNING (state == SDQL2_STATE_EXECUTING || state == SDQL2_STATE_SEARCHING || state == SDQL2_STATE_SWITCHING || state == SDQL2_STATE_PRESEARCH) +#define SDQL2_HALT_CHECK if(!SDQL2_IS_RUNNING) {state = SDQL2_STATE_HALTING; return FALSE;}; + +#define SDQL2_TICK_CHECK ((options & SDQL2_OPTION_HIGH_PRIORITY) ? CHECK_TICK_HIGH_PRIORITY : CHECK_TICK) + +#define SDQL2_STAGE_SWITCH_CHECK if(state != SDQL2_STATE_SWITCHING){\ + if(state == SDQL2_STATE_HALTING){\ + state = SDQL2_STATE_IDLE;\ + return FALSE}\ + state = SDQL2_STATE_ERROR;\ + CRASH("SDQL2 fatal error");}; /client/proc/SDQL2_query(query_text as message) - set category = "Admin" + set category = "Debug" if(!check_rights(R_DEBUG)) //Shouldn't happen... but just to be safe. - message_admins("ERROR: Non-admin [usr.key] attempted to execute a SDQL query!") - log_admin("Non-admin [usr.key] attempted to execute a SDQL query!",level=2,ckey=key_name(usr)) - - if(!query_text || length(query_text) < 1) + message_admins(SPAN_DANGER("ERROR: Non-admin [key_name(usr)] attempted to execute a SDQL query!")) + log_admin("non-admin attempted to execute a SDQL query!") + return FALSE + var/prompt = tgui_alert(usr, "Run SDQL2 Query?", "SDQL2", list("Yes", "Cancel")) + if (prompt != "Yes") return + var/list/results = world.SDQL2_query(query_text, key_name_admin(usr), "[key_name(usr)]") + if(length(results) == 3) + for(var/I in 1 to 3) + to_chat(usr, results[I], confidential = TRUE) +/world/proc/SDQL2_query(query_text, log_entry1, log_entry2, silent = FALSE) + var/query_log = "executed SDQL query(s): \"[query_text]\"." + if(!silent) + message_admins("[log_entry1] [query_log]") + query_log = "[log_entry2] [query_log]" + log_admin(query_log) + + var/start_time_total = REALTIMEOFDAY + var/sequential = FALSE + + if(!length(query_text)) + return var/list/query_list = SDQL2_tokenize(query_text) - - if(!query_list || query_list.len < 1) + if(!length(query_list)) return - - var/list/query_tree = SDQL_parse(query_list) - - if(query_tree.len < 1) + var/list/querys = SDQL_parse(query_list) + if(!length(querys)) return + var/list/datum/sdql2_query/running = list() + var/list/datum/sdql2_query/waiting_queue = list() //Sequential queries queue. - var/list/from_objs = list() - var/list/select_types = list() + for(var/list/query_tree in querys) + var/datum/sdql2_query/query = new /datum/sdql2_query(query_tree) + if(QDELETED(query)) + continue + if(usr) + query.show_next_to_key = usr.ckey + waiting_queue += query + if(query.options & SDQL2_OPTION_SEQUENTIAL) + sequential = TRUE + if(sequential) //Start first one + var/datum/sdql2_query/query = popleft(waiting_queue) + running += query + var/msg = "Starting query #[query.id] - [query.get_query_text()]." + if(usr) + to_chat(usr, SPAN_NOTICE("[msg]"), confidential = TRUE) + log_admin(msg) + query.ARun() + else //Start all + for(var/datum/sdql2_query/query in waiting_queue) + running += query + var/msg = "Starting query #[query.id] - [query.get_query_text()]." + if(usr) + to_chat(usr, SPAN_NOTICE("[msg]"), confidential = TRUE) + log_admin(msg) + query.ARun() + + var/finished = FALSE + var/objs_all = 0 + var/objs_eligible = 0 + var/selectors_used = FALSE + var/list/combined_refs = list() + do + CHECK_TICK + finished = TRUE + for(var/i in running) + var/datum/sdql2_query/query = i + if(QDELETED(query)) + running -= query + continue + else if(query.state != SDQL2_STATE_IDLE) + finished = FALSE + if(query.state == SDQL2_STATE_ERROR) + if(usr) + to_chat(usr, SPAN_NOTICE("SDQL query [query.get_query_text()] errored. It will NOT be automatically garbage collected. Please remove manually."), confidential = TRUE) + running -= query + else + if(query.finished) + objs_all += islist(query.obj_count_all)? length(query.obj_count_all) : query.obj_count_all + objs_eligible += islist(query.obj_count_eligible)? length(query.obj_count_eligible) : query.obj_count_eligible + selectors_used |= query.where_switched + combined_refs |= query.select_refs + running -= query + if(!(query.options & SDQL2_OPTION_DO_NOT_AUTOGC)) + QDEL_IN(query, 50) + if(sequential && waiting_queue.len) + finished = FALSE + var/datum/sdql2_query/next_query = popleft(waiting_queue) + running += next_query + var/msg = "Starting query #[next_query.id] - [next_query.get_query_text()]." + if(usr) + to_chat(usr, SPAN_NOTICE("[msg]"), confidential = TRUE) + log_admin(msg) + next_query.ARun() + else + if(usr) + to_chat(usr, SPAN_NOTICE("SDQL query [query.get_query_text()] was halted. It will NOT be automatically garbage collected. Please remove manually."), confidential = TRUE) + running -= query + while(!finished) + + var/end_time_total = REALTIMEOFDAY - start_time_total + return list(SPAN_NOTICE("SDQL query combined results: [query_text]"),\ + SPAN_NOTICE("SDQL query completed: [objs_all] objects selected by path, and [selectors_used ? objs_eligible : objs_all] objects executed on after WHERE filtering/MAPping if applicable."),\ + SPAN_NOTICE("SDQL combined querys took [DisplayTimeText(end_time_total)] to complete.")) + combined_refs + +var/global/list/sdql2_queries =list() +var/global/obj/effect/statclick/sdql2_vv_all/sdql2_vv_statobj = new(null, "VIEW VARIABLES (all)", null) + +/datum/sdql2_query + var/list/query_tree + var/state = SDQL2_STATE_IDLE + var/options = SDQL2_OPTIONS_DEFAULT + var/superuser = FALSE //Run things like proccalls without using admin protections + var/allow_admin_interact = TRUE //Allow admins to do things to this excluding varedit these two vars + var/static/id_assign = 1 + var/id = 0 + + var/qdel_on_finish = FALSE + + //Last run + //General + var/finished = FALSE + var/start_time + var/end_time + var/where_switched = FALSE + var/show_next_to_key + //Select query only + var/list/select_refs + var/list/select_text + //Runtime tracked + //These three are weird. For best performance, they are only a number when they're not being changed by the SDQL searching/execution code. They only become numbers when they finish changing. + var/list/obj_count_all + var/list/obj_count_eligible + var/obj_count_finished + + //Statclick + var/obj/effect/statclick/SDQL2_delete/delete_click + var/obj/effect/statclick/SDQL2_action/action_click + +/datum/sdql2_query/New(list/tree, SU = FALSE, admin_interact = TRUE, _options = SDQL2_OPTIONS_DEFAULT, finished_qdel = FALSE) + if(!LAZYLEN(tree)) + qdel(src) + return + LAZYADD(sdql2_queries, src) + superuser = SU + allow_admin_interact = admin_interact + query_tree = tree + options = _options + id = id_assign++ + qdel_on_finish = finished_qdel + +/datum/sdql2_query/Destroy() + state = SDQL2_STATE_HALTING + query_tree = null + obj_count_all = null + obj_count_eligible = null + obj_count_finished = null + select_text = null + select_refs = null + sdql2_queries -= src + return ..() + +/datum/sdql2_query/proc/get_query_text() + var/list/out = list() + recursive_list_print(out, query_tree) + return out.Join() + +/proc/recursive_list_print(list/output = list(), list/input, datum/callback/datum_handler, datum/callback/atom_handler) + output += "\[ " + for(var/i in 1 to input.len) + var/final = i == input.len + var/key = input[i] + + //print the key + if(islist(key)) + recursive_list_print(output, key, datum_handler, atom_handler) + else if(isdatum(key) && (datum_handler || (isatom(key) && atom_handler))) + if(isatom(key) && atom_handler) + output += atom_handler.Invoke(key) + else + output += datum_handler.Invoke(key) + else + output += "[key]" + + //print the value + var/is_value = (!isnum(key) && !isnull(input[key])) + if(is_value) + var/value = input[key] + if(islist(value)) + recursive_list_print(output, value, datum_handler, atom_handler) + else if(isdatum(value) && (datum_handler || (isatom(value) && atom_handler))) + if(isatom(value) && atom_handler) + output += atom_handler.Invoke(value) + else + output += datum_handler.Invoke(value) + else + output += " = [value]" + + if(!final) + output += " , " + + output += " \]" + +/datum/sdql2_query/proc/text_state() + switch(state) + if(SDQL2_STATE_ERROR) + return "###ERROR" + if(SDQL2_STATE_IDLE) + return "####IDLE" + if(SDQL2_STATE_PRESEARCH) + return "PRESEARCH" + if(SDQL2_STATE_SEARCHING) + return "SEARCHING" + if(SDQL2_STATE_EXECUTING) + return "EXECUTING" + if(SDQL2_STATE_SWITCHING) + return "SWITCHING" + if(SDQL2_STATE_HALTING) + return "##HALTING" + +/datum/sdql2_query/proc/generate_stat() + if(!allow_admin_interact) + return + if(!delete_click) + delete_click = new(null, "INITIALIZING", src) + if(!action_click) + action_click = new(null, "INITIALIZNG", src) + var/list/L = list() + L[++L.len] = list("[id] ", "[delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \ + [islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\ + [islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\ + [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]")]", ref(delete_click)) + L[++L.len] = list(" ", "[action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]")]", ref(action_click)) + return L + +/datum/sdql2_query/proc/delete_click() + admin_del(usr) + +/datum/sdql2_query/proc/action_click() + if(SDQL2_IS_RUNNING) + admin_halt(usr) + else + admin_run(usr) + +/datum/sdql2_query/proc/admin_halt(user = usr) + if(!SDQL2_IS_RUNNING) + return + var/msg = "[key_name(user)] has halted query #[id]" + message_admins(msg) + log_admin(msg) + state = SDQL2_STATE_HALTING + +/datum/sdql2_query/proc/admin_run(mob/user = usr) + if(SDQL2_IS_RUNNING) + return + var/msg = "[key_name(user)] has (re)started query #[id]" + message_admins(msg) + log_admin(msg) + show_next_to_key = user.ckey + ARun() + +/datum/sdql2_query/proc/admin_del(user = usr) + var/msg = "[key_name(user)] has stopped + deleted query #[id]" + message_admins(msg) + log_admin(msg) + qdel(src) + +/datum/sdql2_query/proc/set_option(name, value) + switch(name) + if("select") + switch(value) + if("force_nulls") + options &= ~(SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + if("proccall") + switch(value) + if("blocking") + options |= SDQL2_OPTION_BLOCKING_CALLS + if("priority") + switch(value) + if("high") + options |= SDQL2_OPTION_HIGH_PRIORITY + if("autogc") + switch(value) + if("keep_alive") + options |= SDQL2_OPTION_DO_NOT_AUTOGC + if("sequential") + switch(value) + if("true") + options |= SDQL2_OPTION_SEQUENTIAL + +/datum/sdql2_query/proc/ARun() + INVOKE_ASYNC(src, PROC_REF(Run)) + +/datum/sdql2_query/proc/Run() + if(SDQL2_IS_RUNNING) + return FALSE + if(query_tree["options"]) + for(var/name in query_tree["options"]) + var/value = query_tree["options"][name] + set_option(name, value) + select_refs = list() + select_text = null + obj_count_all = 0 + obj_count_eligible = 0 + obj_count_finished = 0 + start_time = REALTIMEOFDAY + + state = SDQL2_STATE_PRESEARCH + var/list/search_tree = PreSearch() + SDQL2_STAGE_SWITCH_CHECK + + state = SDQL2_STATE_SEARCHING + var/list/found = Search(search_tree) + SDQL2_STAGE_SWITCH_CHECK + + state = SDQL2_STATE_EXECUTING + Execute(found) + SDQL2_STAGE_SWITCH_CHECK + + end_time = REALTIMEOFDAY + state = SDQL2_STATE_IDLE + finished = TRUE + . = TRUE + if(show_next_to_key) + var/client/C = directory[show_next_to_key] + if(C) + var/mob/showmob = C.mob + to_chat(showmob, "SDQL query results: [get_query_text()]
\ + SDQL query completed: [islist(obj_count_all)? length(obj_count_all) : obj_count_all] objects selected by path, and \ + [where_switched? "[islist(obj_count_eligible)? length(obj_count_eligible) : obj_count_eligible] objects executed on after WHERE keyword selection." : ""]
\ + SDQL query took [DisplayTimeText(end_time - start_time)] to complete.
", confidential = TRUE) + if(length(select_text)) + var/text = islist(select_text)? select_text.Join() : select_text + var/static/result_offset = 0 + showmob << browse(text, "window=SDQL-result-[result_offset++]") + show_next_to_key = null + if(qdel_on_finish) + qdel(src) + +/datum/sdql2_query/proc/PreSearch() + SDQL2_HALT_CHECK switch(query_tree[1]) if("explain") SDQL_testout(query_tree["explain"]) + state = SDQL2_STATE_HALTING return - if("call") - if("on" in query_tree) - select_types = query_tree["on"] - else - return - + . = query_tree["on"] if("select", "delete", "update") - select_types = query_tree[query_tree[1]] + . = query_tree[query_tree[1]] + state = SDQL2_STATE_SWITCHING - from_objs = SDQL_from_objs(query_tree["from"]) +/datum/sdql2_query/proc/Search(list/tree) + SDQL2_HALT_CHECK + var/type = tree[1] + var/list/from = tree[2] + var/list/objs = SDQL_from_objs(from) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + objs = SDQL_get_all(type, objs) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK - var/list/objs = list() + // 1 and 2 are type and FROM. + var/i = 3 + while (i <= tree.len) + var/key = tree[i++] + var/list/expression = tree[i++] + switch (key) + if ("map") + for(var/j = 1 to objs.len) + var/x = objs[j] + objs[j] = SDQL_expression(x, expression) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK - for(var/type in select_types) - var/char = copytext(type, 1, 2) + if ("where") + where_switched = TRUE + var/list/out = list() + obj_count_eligible = out + for(var/x in objs) + if(SDQL_expression(x, expression)) + out += x + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + objs = out + if(islist(obj_count_eligible)) + obj_count_eligible = objs.len + else + obj_count_eligible = obj_count_all + . = objs + state = SDQL2_STATE_SWITCHING - if(char == "/" || char == "*") - for(var/from in from_objs) - objs += SDQL_get_all(type, from) +/datum/sdql2_query/proc/SDQL_from_objs(list/tree) + if("world" in tree) + return world + return SDQL_expression(world, tree) - else if(char == "'" || char == "\"") - objs += locate(copytext(type, 2, length(type))) +/datum/sdql2_query/proc/SDQL_get_all(type, location) + var/list/out = list() + obj_count_all = out - if("where" in query_tree) - var/objs_temp = objs - objs = list() - for(var/datum/d in objs_temp) - if(SDQL_expression(d, query_tree["where"])) - objs += d +// If only a single object got returned, wrap it into a list so the for loops run on it. + if(!islist(location) && location != world) + location = list(location) - //to_chat(usr, "Query: [query_text]") - var/static/list/blacklist = list(/datum/configuration, /datum/controller/subsystem/discord) - for(var/datum/D in objs) - if(blacklist[D.type]) - objs -= D - message_admins("[usr] executed SDQL query: \"[query_text]\".") + if(type == "*") + for(var/i in location) + var/datum/d = i + if(d.can_vv_get() || superuser) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + return out + if(istext(type)) + type = text2path(type) + var/typecache = typecacheof(type) + if(ispath(type, /mob)) + for(var/mob/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /turf)) + for(var/turf/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /obj)) + for(var/obj/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /area)) + for(var/area/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /atom)) + for(var/atom/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /datum)) + if(location == world) //snowflake for byond shortcut + for(var/datum/d) //stupid byond trick to have it not return atoms to make this less laggy + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + else + for(var/datum/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + obj_count_all = out.len + return out + +/datum/sdql2_query/proc/Execute(list/found) + SDQL2_HALT_CHECK + select_refs = list() + select_text = list() switch(query_tree[1]) + if("call") + for(var/i in found) + if(!isdatum(i)) + continue + world.SDQL_var(i, query_tree["call"][1], null, i, superuser, src) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + if("delete") - for(var/datum/d in objs) + for(var/datum/d in found) qdel(d) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK if("select") - var/text = "" - for(var/datum/t in objs) - if(istype(t, /atom)) - var/atom/a = t - - if(a.x) - text += "\ref[t]: [t] at ([a.x], [a.y], [a.z])
" - - else if(a.loc && a.loc.x) - text += "\ref[t]: [t] in [a.loc] at ([a.loc.x], [a.loc.y], [a.loc.z])
" - - else - text += "\ref[t]: [t]
" - - else - text += "\ref[t]: [t]
" - - usr << browse(text, "window=SDQL-result") + var/list/text_list = list() + var/print_nulls = !(options & SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + obj_count_finished = select_refs + for(var/i in found) + SDQL_print(i, text_list, print_nulls) + select_refs[ref(i)] = TRUE + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + select_text = text_list if("update") if("set" in query_tree) var/list/set_list = query_tree["set"] - for(var/datum/d in objs) - var/list/vals = list() - for(var/v in set_list) - if(v in d.vars) - vals += v - vals[v] = SDQL_expression(d, set_list[v]) - - if(istype(d, /turf)) - for(var/v in vals) - if(v == "x" || v == "y" || v == "z") - continue - - d.vars[v] = vals[v] - - else - for(var/v in vals) - d.vars[v] = vals[v] - - - - - -/proc/SDQL_parse(list/query_list) - var/datum/SDQL_parser/parser = new(query_list) - var/list/query_tree = parser.parse() - - qdel(parser) - - return query_tree - - - -/proc/SDQL_testout(list/query_tree, indent = 0) - var/spaces = "" - for(var/s = 0, s < indent, s++) - spaces += " " - - for(var/item in query_tree) - if(istype(item, /list)) - to_world("[spaces](") - SDQL_testout(item, indent + 1) - to_world("[spaces])") - - else - to_world("[spaces][item]") - - if(!isnum(item) && query_tree[item]) - - if(istype(query_tree[item], /list)) - to_world("[spaces] (") - SDQL_testout(query_tree[item], indent + 2) - to_world("[spaces] )") + for(var/d in found) + if(!isdatum(d)) + continue + SDQL_internal_vv(d, set_list) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + if(islist(obj_count_finished)) + obj_count_finished = length(obj_count_finished) + state = SDQL2_STATE_SWITCHING +/datum/sdql2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE) + if(isdatum(object)) + text_list += "[ref(object)] : [object]" + if(istype(object, /atom)) + var/atom/A = object + var/turf/T = A.loc + var/area/a + if(istype(T)) + text_list += " at [T] (JMP)" + a = T.loc else - to_world("[spaces] [query_tree[item]]") - - - -/proc/SDQL_from_objs(list/tree) - if("world" in tree) - return list(world) - - var/list/out = list() - - for(var/type in tree) - var/char = copytext(type, 1, 2) - - if(char == "/") - out += SDQL_get_all(type, world) - - else if(char == "'" || char == "\"") - out += locate(copytext(type, 2, length(type))) - - return out - - -/proc/SDQL_get_all(type, location) - var/list/out = list() - - if(type == "*") - for(var/datum/d in location) - out += d - - return out - - type = text2path(type) - - if(ispath(type, /mob)) - for(var/mob/d in location) - if(istype(d, type)) - out += d - - else if(ispath(type, /turf)) - for(var/turf/d in location) - if(istype(d, type)) - out += d - - else if(ispath(type, /obj)) - for(var/obj/d in location) - if(istype(d, type)) - out += d - - else if(ispath(type, /area)) - for(var/area/d in location) - if(istype(d, type)) - out += d - - else if(ispath(type, /atom)) - for(var/atom/d in location) - if(istype(d, type)) - out += d - + var/turf/final = get_turf(T) //Recursive, hopefully? + if(istype(final)) + text_list += " at [final] (JMP)" + a = final.loc + else + text_list += " at nonexistent location" + if(a) + text_list += " in area [a]" + if(T.loc != a) + text_list += " inside [T]" + text_list += "
" + else if(islist(object)) + var/list/L = object + var/first = TRUE + text_list += "\[" + for (var/x in L) + if (!first) + text_list += ", " + first = FALSE + SDQL_print(x, text_list) + if (!isnull(x) && !isnum(x) && L[x] != null) + text_list += " -> " + SDQL_print(L[L[x]]) + text_list += "]
" else - for(var/datum/d in location) - if(istype(d, type)) - out += d + if(isnull(object)) + if(print_nulls) + text_list += "NULL
" + else + text_list += "[object]
" - return out +/datum/sdql2_query/vv_edit_var(var_name, var_value) + if(!allow_admin_interact) + return FALSE + if(var_name == NAMEOF(src, superuser) || var_name == NAMEOF(src, allow_admin_interact) || var_name == NAMEOF(src, query_tree)) + return FALSE + return ..() +/datum/sdql2_query/proc/SDQL_internal_vv(d, list/set_list) + for(var/list/sets in set_list) + var/datum/temp = d + var/i = 0 + for(var/v in sets) + if(v == "#null") + SDQL_expression(d, set_list[sets]) + break + i++ + if(i == sets.len) + if(superuser) + if(temp.vars.Find(v)) + temp.vars[v] = SDQL_expression(d, set_list[sets]) + else + temp.vv_edit_var(v, SDQL_expression(d, set_list[sets])) + break + if(temp.vars.Find(v) && (istype(temp.vars[v], /datum) || istype(temp.vars[v], /client))) + temp = temp.vars[v] + else + break -/proc/SDQL_expression(datum/object, list/expression, start = 1) +/datum/sdql2_query/proc/SDQL_function_blocking(datum/object, procname, list/arguments, source) + var/list/new_args = list() + for(var/arg in arguments) + new_args[++new_args.len] = SDQL_expression(source, arg) + if(object == "global") // Global proc. + return call("/proc/[procname]")(arglist(new_args)) + return call(object, procname)(arglist(new_args)) + +/datum/sdql2_query/proc/SDQL_function_async(datum/object, procname, list/arguments, source) + set waitfor = FALSE + return SDQL_function_blocking(object, procname, arguments, source) + +/datum/sdql2_query/proc/SDQL_expression(datum/object, list/expression, start = 1) var/result = 0 var/val @@ -230,19 +815,21 @@ if(op != "") switch(op) if("+") - result += val + result = (result + val) if("-") - result -= val + result = (result - val) if("*") - result *= val + result = (result * val) if("/") - result /= val + result = (result / val) if("&") - result &= val + result = (result & val) if("|") - result |= val + result = (result | val) if("^") - result ^= val + result = (result ^ val) + if("%") + result = (result % val) if("=", "==") result = (result == val) if("!=", "<>") @@ -260,14 +847,14 @@ if("or", "||") result = (result || val) else - to_chat(usr, "SDQL2: Unknown op [op]") + to_chat(usr, SPAN_DANGER("SDQL2: Unknown op [op]"), confidential = TRUE) result = null else result = val return result -/proc/SDQL_value(datum/object, list/expression, start = 1) +/datum/sdql2_query/proc/SDQL_value(datum/object, list/expression, start = 1) var/i = start var/val = null @@ -277,6 +864,12 @@ if(istype(expression[i], /list)) val = SDQL_expression(object, expression[i]) + else if(expression[i] == "TRUE") + val = TRUE + + else if(expression[i] == "FALSE") + val = FALSE + else if(expression[i] == "!") var/list/ret = SDQL_value(object, expression, i + 1) val = !ret["val"] @@ -298,44 +891,213 @@ else if(isnum(expression[i])) val = expression[i] - else if(copytext(expression[i], 1, 2) in list("'", "\"")) - val = copytext(expression[i], 2, length(expression[i])) + else if(ispath(expression[i])) + val = expression[i] + + else if(expression[i][1] in list("'", "\"")) + val = copytext_char(expression[i], 2, -1) + + else if(expression[i] == "\[") + var/list/expressions_list = expression[++i] + val = list() + for(var/list/expression_list in expressions_list) + var/result = SDQL_expression(object, expression_list) + var/assoc + if(expressions_list[expression_list] != null) + assoc = SDQL_expression(object, expressions_list[expression_list]) + if(assoc != null) + // Need to insert the key like this to prevent duplicate keys fucking up. + var/list/dummy = list() + dummy[result] = assoc + result = dummy + val += result + + else if(expression[i] == "@\[") + var/list/search_tree = expression[++i] + var/already_searching = (state == SDQL2_STATE_SEARCHING) //In case we nest, don't want to break out of the searching state until we're all done. + + if(!already_searching) + state = SDQL2_STATE_SEARCHING + + val = Search(search_tree) + SDQL2_STAGE_SWITCH_CHECK + + if(!already_searching) + state = SDQL2_STATE_EXECUTING + else + state = SDQL2_STATE_SEARCHING else - val = SDQL_var(object, expression, i) + val = world.SDQL_var(object, expression, i, object, superuser, src) i = expression.len return list("val" = val, "i" = i) -/proc/SDQL_var(datum/object, list/expression, start = 1) +/proc/SDQL_parse(list/query_list) + var/datum/sdql_parser/parser = new() + var/list/querys = list() + var/list/query_tree = list() + var/pos = 1 + var/querys_pos = 1 + var/do_parse = 0 - if(expression[start] in object.vars) + for(var/val in query_list) + if(val == ";") + do_parse = 1 + else if(pos >= query_list.len) + query_tree += val + do_parse = 1 - if(start < expression.len && expression[start + 1] == ".") - return SDQL_var(object.vars[expression[start]], expression[start + 2]) + if(do_parse) + parser.query = query_tree + var/list/parsed_tree + parsed_tree = parser.parse() + if(parsed_tree.len > 0) + querys.len = querys_pos + querys[querys_pos] = parsed_tree + querys_pos++ + else //There was an error so don't run anything, and tell the user which query has errored. + to_chat(usr, SPAN_DANGER("Parsing error on [querys_pos]\th query. Nothing was executed."), confidential = TRUE) + return list() + query_tree = list() + do_parse = 0 + else + query_tree += val + pos++ + + qdel(parser) + return querys + +/proc/SDQL_testout(list/query_tree, indent = 0) + var/static/whitespace = "    " + var/spaces = "" + if(indent > 0) + for(var/i in 1 to indent) + spaces += whitespace + + for(var/item in query_tree) + if(istype(item, /list)) + to_chat(usr, "[spaces](", confidential = TRUE) + SDQL_testout(item, indent + 1) + to_chat(usr, "[spaces])", confidential = TRUE) else - return object.vars[expression[start]] + to_chat(usr, "[spaces][item]", confidential = TRUE) - else + if(!isnum(item) && query_tree[item]) + + if(istype(query_tree[item], /list)) + to_chat(usr, "[spaces][whitespace](", confidential = TRUE) + SDQL_testout(query_tree[item], indent + 2) + to_chat(usr, "[spaces][whitespace])", confidential = TRUE) + + else + to_chat(usr, "[spaces][whitespace][query_tree[item]]", confidential = TRUE) + +//Staying as a world proc as this is called too often for changes to offset the potential IsAdminAdvancedProcCall checking overhead. +/world/proc/SDQL_var(object, list/expression, start = 1, source, superuser, datum/sdql2_query/query) + var/v + var/static/list/exclude = list("usr", "src", "marked", "global", "MC", "FS", "CFG") + var/long = start < expression.len + var/datum/D + if(isdatum(object)) + D = object + + if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude) && copytext(expression[start], 1, 3) != "SS") //3 == length("SS") + 1 + to_chat(usr, SPAN_DANGER("World variables are not allowed to be accessed. Use global."), confidential = TRUE) return null + else if(expression [start] == "{" && long) + if(lowertext(copytext(expression[start + 1], 1, 3)) != "0x") //3 == length("0x") + 1 + to_chat(usr, SPAN_DANGER("Invalid pointer syntax: [expression[start + 1]]"), confidential = TRUE) + return null + var/datum/located = locate("\[[expression[start + 1]]]") + if(!istype(located)) + to_chat(usr, SPAN_DANGER("Invalid pointer: [expression[start + 1]] - null or not datum"), confidential = TRUE) + return null + v = located + start++ + long = start < expression.len + else if(expression[start] == "(" && long) + v = query.SDQL_expression(source, expression[start + 1]) + start++ + long = start < expression.len + else if(D != null && (!long || expression[start + 1] == ".") && (expression[start] in D.vars)) + if(D.can_vv_get(expression[start]) || superuser) + v = D.vars[expression[start]] + else + v = "SECRET" + else if(D != null && long && expression[start + 1] == ":" && hascall(D, expression[start])) + v = expression[start] + else if(!long || expression[start + 1] == ".") + switch(expression[start]) + if("usr") + v = usr + if("src") + v = source + if("marked") + if(usr.client && usr.client.holder && usr.client.holder.marked_datum) + v = usr.client.holder.marked_datum + else + return null + if("world") + v = world + if("global") + v = "global" + if("MC") + v = Master + if("FS") + v = Failsafe + if("CFG") + v = config + else + if(copytext(expression[start], 1, 3) == "SS") //Subsystem //3 == length("SS") + 1 + var/SSname = copytext_char(expression[start], 3) + var/SSlength = length(SSname) + var/datum/controller/subsystem/SS + var/SSmatch + for(var/_SS in Master.subsystems) + SS = _SS + if(copytext("[SS.type]", -SSlength) == SSname) + SSmatch = SS + break + if(!SSmatch) + return null + v = SSmatch + else + return null + if(long) + if(expression[start + 1] == ".") + return SDQL_var(v, expression[start + 2], null, source, superuser, query) + else if(expression[start + 1] == ":") + return (query.options & SDQL2_OPTION_BLOCKING_CALLS)? query.SDQL_function_async(object, v, expression[start + 2], source) : query.SDQL_function_blocking(object, v, expression[start + 2], source) + else if(expression[start + 1] == "\[" && islist(v)) + var/list/L = v + var/index = query.SDQL_expression(source, expression[start + 2]) + if(isnum(index) && (!ISINTEGER(index) || L.len < index)) + to_chat(usr, SPAN_DANGER("Invalid list index: [index]"), confidential = TRUE) + return null + return L[index] + return v + /proc/SDQL2_tokenize(query_text) var/list/whitespace = list(" ", "\n", "\t") - var/list/single = list("(", ")", ",", "+", "-", ".") + var/list/single = list("(", ")", ",", "+", "-", ".", "\[", "]", "{", "}", ";", ":") var/list/multi = list( "=" = list("", "="), "<" = list("", "=", ">"), ">" = list("", "="), - "!" = list("", "=")) + "!" = list("", "="), + "@" = list("\[")) var/word = "" var/list/query_list = list() var/len = length(query_text) + var/char = "" - for(var/i = 1, i <= len, i++) - var/char = copytext(query_text, i, i + 1) + for(var/i = 1, i <= len, i += length(char)) + char = query_text[i] if(char in whitespace) if(word != "") @@ -354,7 +1116,7 @@ query_list += word word = "" - var/char2 = copytext(query_text, i + 1, i + 2) + var/char2 = query_text[i + length(char)] if(char2 in multi[char]) query_list += "[char][char2]" @@ -365,18 +1127,18 @@ else if(char == "'") if(word != "") - to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.") + to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.", confidential = TRUE) return null word = "'" - for(i++, i <= len, i++) - char = copytext(query_text, i, i + 1) + for(i += length(char), i <= len, i += length(char)) + char = query_text[i] if(char == "'") - if(copytext(query_text, i + 1, i + 2) == "'") + if(query_text[i + length(char)] == "'") word += "'" - i++ + i += length(query_text[i + length(char)]) else break @@ -385,7 +1147,7 @@ word += char if(i > len) - to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again.") + to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again.", confidential = TRUE) return null query_list += "[word]'" @@ -393,18 +1155,18 @@ else if(char == "\"") if(word != "") - to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.") + to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.", confidential = TRUE) return null word = "\"" - for(i++, i <= len, i++) - char = copytext(query_text, i, i + 1) + for(i += length(char), i <= len, i += length(char)) + char = query_text[i] if(char == "\"") - if(copytext(query_text, i + 1, i + 2) == "'") + if((i + length(char) <= len) && query_text[i + length(char)] == "'") word += "\"" - i++ + i += length(query_text[i + length(char)]) else break @@ -413,7 +1175,7 @@ word += char if(i > len) - to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again.") + to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again.", confidential = TRUE) return null query_list += "[word]\"" @@ -424,5 +1186,48 @@ if(word != "") query_list += word - return query_list + +/obj/effect/statclick/SDQL2_delete/Click() + if(!usr.client?.holder) + message_admins("[key_name_admin(usr)] non-holder clicked on a statclick! ([src])") + log_admin("non-holder clicked on a statclick! ([src])") + return + var/datum/sdql2_query/Q = target + Q.delete_click() + +/obj/effect/statclick/SDQL2_action/Click() + if(!usr.client?.holder) + message_admins("[key_name_admin(usr)] non-holder clicked on a statclick! ([src])") + log_admin("non-holder clicked on a statclick! ([src])") + return + var/datum/sdql2_query/Q = target + Q.action_click() + +/obj/effect/statclick/sdql2_vv_all + name = "VIEW VARIABLES" + +/obj/effect/statclick/sdql2_vv_all/Click() + if(!usr.client?.holder) + message_admins("[key_name_admin(usr)] non-holder clicked on a statclick! ([src])") + log_admin("non-holder clicked on a statclick! ([src])") + return + usr.client.debug_variables(sdql2_queries) + +#undef SDQL2_HALT_CHECK +#undef SDQL2_IS_RUNNING +#undef SDQL2_OPTION_BLOCKING_CALLS +#undef SDQL2_OPTION_DO_NOT_AUTOGC +#undef SDQL2_OPTION_HIGH_PRIORITY +#undef SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS +#undef SDQL2_OPTION_SEQUENTIAL +#undef SDQL2_OPTIONS_DEFAULT +#undef SDQL2_STAGE_SWITCH_CHECK +#undef SDQL2_STATE_ERROR +#undef SDQL2_STATE_EXECUTING +#undef SDQL2_STATE_HALTING +#undef SDQL2_STATE_IDLE +#undef SDQL2_STATE_PRESEARCH +#undef SDQL2_STATE_SEARCHING +#undef SDQL2_STATE_SWITCHING +#undef SDQL2_TICK_CHECK diff --git a/code/modules/admin/verbs/SDQL_2_parser.dm b/code/modules/admin/verbs/SDQL_2_parser.dm index 040a555267f..f07361dd29f 100644 --- a/code/modules/admin/verbs/SDQL_2_parser.dm +++ b/code/modules/admin/verbs/SDQL_2_parser.dm @@ -1,105 +1,148 @@ //I'm pretty sure that this is a recursive [s]descent[/s] ascent parser. - //Spec ////////// // -// query : select_query | delete_query | update_query | call_query | explain -// explain : 'EXPLAIN' query +// query : select_query | delete_query | update_query | call_query | explain +// explain : 'EXPLAIN' query +// select_query : 'SELECT' object_selectors +// delete_query : 'DELETE' object_selectors +// update_query : 'UPDATE' object_selectors 'SET' assignments +// call_query : 'CALL' variable 'ON' object_selectors // Note here: 'variable' does function calls. This simplifies parsing. // -// select_query : 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -// delete_query : 'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -// update_query : 'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression] -// call_query : 'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]] +// select_item : '*' | object_type // -// select_list : select_item [',' select_list] -// select_item : '*' | select_function | object_type -// select_function : count_function -// count_function : 'COUNT' '(' '*' ')' | 'COUNT' '(' object_types ')' +// object_selectors : select_item [('FROM' | 'IN') from_item] [modifier_list] +// modifier_list : ('WHERE' bool_expression | 'MAP' expression) [modifier_list] // -// from_list : from_item [',' from_list] -// from_item : 'world' | object_type +// from_item : 'world' | expression // -// call_function : ['(' [arguments] ')'] -// arguments : expression [',' arguments] +// call_function : '(' [expression_list] ')' // -// object_type : | string +// object_type : // -// assignments : assignment, [',' assignments] -// assignment : '=' expression -// variable : | '.' variable +// assignments : assignment [',' assignments] +// assignment : '=' expression +// variable : | variable '.' variable | variable '[' ']' | '{' '}' | '(' expression ')' | call_function // -// bool_expression : expression comparitor expression [bool_operator bool_expression] -// expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression] -// unary_expression : unary_operator ( unary_expression | value | '(' expression ')' ) -// comparitor : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' -// value : variable | string | number | 'null' -// unary_operator : '!' | '-' | '~' -// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' -// bool_operator : 'AND' | '&&' | 'OR' | '||' +// bool_expression : expression comparator expression [bool_operator bool_expression] +// expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression] +// expression_list : expression [',' expression_list] +// unary_expression : unary_operator ( unary_expression | value ) // -// string : ''' ''' | '"' '"' -// number : +// comparator : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' +// value : variable | string | number | 'null' | object_type | array | selectors_array +// unary_operator : '!' | '-' | '~' +// binary_operator : comparator | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%' +// bool_operator : 'AND' | '&&' | 'OR' | '||' +// +// array : '[' expression_list ']' +// selectors_array : '@[' object_selectors ']' +// +// string : ''' ''' | '"' '"' +// number : // ////////// -/datum/SDQL_parser +#define SDQL2_VALID_OPTION_TYPES list(\ + "autogc",\ + "priority",\ + "proccall",\ + "select",\ + "sequential",\ +) + +#define SDQL2_VALID_OPTION_VALUES list(\ + "async",\ + "blocking",\ + "force_nulls",\ + "high",\ + "keep_alive" ,\ + "normal",\ + "skip_nulls",\ + "true",\ +) + +/datum/sdql_parser var/query_type var/error = 0 var/list/query var/list/tree - var/list/select_functions = list("count") var/list/boolean_operators = list("and", "or", "&&", "||") var/list/unary_operators = list("!", "-", "~") - var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^") - var/list/comparitors = list("=", "==", "!=", "<>", "<", "<=", ">", ">=") + var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^", "%") + var/list/comparators = list("=", "==", "!=", "<>", "<", "<=", ">", ">=") - - -/datum/SDQL_parser/New(query_list) +/datum/sdql_parser/New(query_list) query = query_list - - -/datum/SDQL_parser/proc/parse_error(error_message) +/datum/sdql_parser/proc/parse_error(error_message) error = 1 - to_chat(usr, "SQDL2 Parsing Error: [error_message]") + to_chat(usr, SPAN_WARNING("SDQL2 Parsing Error: [error_message]"), confidential = TRUE) return query.len + 1 -/datum/SDQL_parser/proc/parse() +/datum/sdql_parser/proc/parse() tree = list() - query(1, tree) + query_options(1, tree) if(error) return list() else return tree -/datum/SDQL_parser/proc/token(i) +/datum/sdql_parser/proc/token(i) if(i <= query.len) return query[i] else return null -/datum/SDQL_parser/proc/tokens(i, num) +/datum/sdql_parser/proc/tokens(i, num) if(i + num <= query.len) return query.Copy(i, i + num) else return null -/datum/SDQL_parser/proc/tokenl(i) +/datum/sdql_parser/proc/tokenl(i) return lowertext(token(i)) +/datum/sdql_parser/proc/query_options(i, list/node) + var/list/options = list() + if(tokenl(i) == "using") + i = option_assignments(i + 1, node, options) + query(i, node) + if(length(options)) + node["options"] = options +//option_assignment: query_option '=' define +/datum/sdql_parser/proc/option_assignment(i, list/node, list/assignment_list = list()) + var/type = tokenl(i) + if(!(type in SDQL2_VALID_OPTION_TYPES)) + parse_error("Invalid option type: [type]") + if(!(token(i + 1) == "=")) + parse_error("Invalid option assignment symbol: [token(i + 1)]") + var/val = tokenl(i + 2) + if(!(val in SDQL2_VALID_OPTION_VALUES)) + parse_error("Invalid option value: [val]") + assignment_list[type] = val + return (i + 3) -//query: select_query | delete_query | update_query -/datum/SDQL_parser/proc/query(i, list/node) +//option_assignments: option_assignment, [',' option_assignments] +/datum/sdql_parser/proc/option_assignments(i, list/node, list/store) + i = option_assignment(i, node, store) + + if(token(i) == ",") + i = option_assignments(i + 1, node, store) + + return i + +//query: select_query | delete_query | update_query +/datum/sdql_parser/proc/query(i, list/node) query_type = tokenl(i) switch(query_type) @@ -121,134 +164,98 @@ query(i + 1, node["explain"]) -// select_query: 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -/datum/SDQL_parser/proc/select_query(i, list/node) +// select_query: 'SELECT' object_selectors +/datum/sdql_parser/proc/select_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(i + 1, select) - node += "select" node["select"] = select - - var/list/from = list() - if(tokenl(i) in list("from", "in")) - i = from_list(i + 1, from) - else - from += "world" - - node += "from" - node["from"] = from - - if(tokenl(i) == "where") - var/list/where = list() - i = bool_expression(i + 1, where) - - node += "where" - node["where"] = where - return i -//delete_query: 'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -/datum/SDQL_parser/proc/delete_query(i, list/node) +//delete_query: 'DELETE' object_selectors +/datum/sdql_parser/proc/delete_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(i + 1, select) - node += "delete" node["delete"] = select - var/list/from = list() - if(tokenl(i) in list("from", "in")) - i = from_list(i + 1, from) - else - from += "world" - - node += "from" - node["from"] = from - - if(tokenl(i) == "where") - var/list/where = list() - i = bool_expression(i + 1, where) - - node += "where" - node["where"] = where - return i -//update_query: 'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression] -/datum/SDQL_parser/proc/update_query(i, list/node) +//update_query: 'UPDATE' object_selectors 'SET' assignments +/datum/sdql_parser/proc/update_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(i + 1, select) - node += "update" node["update"] = select - var/list/from = list() - if(tokenl(i) in list("from", "in")) - i = from_list(i + 1, from) - else - from += "world" - - node += "from" - node["from"] = from - if(tokenl(i) != "set") i = parse_error("UPDATE has misplaced SET") var/list/set_assignments = list() i = assignments(i + 1, set_assignments) - node += "set" node["set"] = set_assignments - if(tokenl(i) == "where") - var/list/where = list() - i = bool_expression(i + 1, where) - - node += "where" - node["where"] = where - return i -//call_query: 'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]] -/datum/SDQL_parser/proc/call_query(i, list/node) +//call_query: 'CALL' call_function ['ON' object_selectors] +/datum/sdql_parser/proc/call_query(i, list/node) var/list/func = list() - i = call_function(i + 1, func) + i = variable(i + 1, func) // Yes technically does anything variable() matches but I don't care, if admins fuck up this badly then they shouldn't be allowed near SDQL. - node += "call" node["call"] = func if(tokenl(i) != "on") - return i + return parse_error("You need to specify what to call ON.") var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(i + 1, select) - node += "on" node["on"] = select - var/list/from = list() - if(tokenl(i) in list("from", "in")) - i = from_list(i + 1, from) - else - from += "world" - - node += "from" - node["from"] = from - - if(tokenl(i) == "where") - var/list/where = list() - i = bool_expression(i + 1, where) - - node += "where" - node["where"] = where - return i +// object_selectors: select_item [('FROM' | 'IN') from_item] [modifier_list] +/datum/sdql_parser/proc/object_selectors(i, list/node) + i = select_item(i, node) -//select_list: select_item [',' select_list] -/datum/SDQL_parser/proc/select_list(i, list/node) + if (tokenl(i) == "from" || tokenl(i) == "in") + i++ + var/list/from = list() + i = from_item(i, from) + node[++node.len] = from + + else + node[++node.len] = list("world") + + i = modifier_list(i, node) + return i + +// modifier_list: ('WHERE' bool_expression | 'MAP' expression) [modifier_list] +/datum/sdql_parser/proc/modifier_list(i, list/node) + while (TRUE) + if (tokenl(i) == "where") + i++ + node += "where" + var/list/expr = list() + i = bool_expression(i, expr) + node[++node.len] = expr + + else if (tokenl(i) == "map") + i++ + node += "map" + var/list/expr = list() + i = expression(i, expr) + node[++node.len] = expr + + else + return i + +//select_list:select_item [',' select_list] +/datum/sdql_parser/proc/select_list(i, list/node) i = select_item(i, node) if(token(i) == ",") @@ -256,19 +263,8 @@ return i - -//from_list: from_item [',' from_list] -/datum/SDQL_parser/proc/from_list(i, list/node) - i = from_item(i, node) - - if(token(i) == ",") - i = from_list(i + 1, node) - - return i - - -//assignments: assignment, [',' assignments] -/datum/SDQL_parser/proc/assignments(i, list/node) +//assignments: assignment, [',' assignments] +/datum/sdql_parser/proc/assignments(i, list/node) i = assignment(i, node) if(token(i) == ",") @@ -277,35 +273,61 @@ return i -//select_item: '*' | select_function | object_type -/datum/SDQL_parser/proc/select_item(i, list/node) - if(token(i) == "*") +//select_item: '*' | select_function | object_type +/datum/sdql_parser/proc/select_item(i, list/node) + if (token(i) == "*") node += "*" i++ - else if(tokenl(i) in select_functions) - i = select_function(i, node) + else if(token(i)[1] == "/") + i = object_type(i, node) else - i = object_type(i, node) + i = parse_error("Expected '*' or type path for select item") return i +// Standardized method for handling the IN/FROM and WHERE options. +/datum/sdql_parser/proc/selectors(i, list/node) + while (token(i)) + var/tok = tokenl(i) + if (tok in list("from", "in")) + var/list/from = list() + i = from_item(i + 1, from) -//from_item: 'world' | object_type -/datum/SDQL_parser/proc/from_item(i, list/node) + node["from"] = from + continue + + if (tok == "where") + var/list/where = list() + i = bool_expression(i + 1, where) + + node["where"] = where + continue + + parse_error("Expected either FROM, IN or WHERE token, found [token(i)] instead.") + return i + 1 + + if (!node.Find("from")) + node["from"] = list("world") + + return i + +//from_item: 'world' | expression +/datum/sdql_parser/proc/from_item(i, list/node) if(token(i) == "world") node += "world" i++ else - i = object_type(i, node) + i = expression(i, node) return i -//bool_expression: expression [bool_operator bool_expression] -/datum/SDQL_parser/proc/bool_expression(i, list/node) +//bool_expression: expression [bool_operator bool_expression] +/datum/sdql_parser/proc/bool_expression(i, list/node) + var/list/bool = list() i = expression(i, bool) @@ -318,15 +340,18 @@ return i -//assignment: '=' expression -/datum/SDQL_parser/proc/assignment(i, list/node) - node += token(i) +//assignment: '=' expression +/datum/sdql_parser/proc/assignment(i, list/node, list/assignment_list = list()) + assignment_list += token(i) - if(token(i + 1) == "=") - var/varname = token(i) - node[varname] = list() + if(token(i + 1) == ".") + i = assignment(i + 2, node, assignment_list) - i = expression(i + 2, node[varname]) + else if(token(i + 1) == "=") + var/exp_list = list() + node[assignment_list] = exp_list + + i = expression(i + 2, exp_list) else parse_error("Assignment expected, but no = found") @@ -334,57 +359,97 @@ return i -//variable: | '.' variable -/datum/SDQL_parser/proc/variable(i, list/node) +//variable: | variable '.' variable | variable '[' ']' | '{' '}' | '(' expression ')' | call_function +/datum/sdql_parser/proc/variable(i, list/node) var/list/L = list(token(i)) node[++node.len] = L + if(token(i) == "{") + L += token(i + 1) + i += 2 + + if(token(i) != "}") + parse_error("Missing } at end of pointer.") + + else if(token(i) == "(") // not a proc but an expression + var/list/sub_expression = list() + + i = expression(i + 1, sub_expression) + + if(token(i) != ")") + parse_error("Missing ) at end of expression.") + + L[++L.len] = sub_expression + if(token(i + 1) == ".") L += "." i = variable(i + 2, L) + else if (token(i + 1) == "(") // OH BOY PROC + var/list/arguments = list() + i = call_function(i, null, arguments) + L += ":" + L[++L.len] = arguments + + else if (token(i + 1) == "\[") + var/list/expression = list() + i = expression(i + 2, expression) + if (token(i) != "]") + parse_error("Missing ] at the end of list access.") + + L += "\[" + L[++L.len] = expression + i++ + else i++ return i -//object_type: | string -/datum/SDQL_parser/proc/object_type(i, list/node) - if(copytext(token(i), 1, 2) == "/") - node += token(i) +//object_type: +/datum/sdql_parser/proc/object_type(i, list/node) - else - i = string(i, node) + if(token(i)[1] != "/") + return parse_error("Expected type, but it didn't begin with /") + + var/path = text2path(token(i)) + if (path == null) + return parse_error("Nonexistent type path: [token(i)]") + + node += path return i + 1 -//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' -/datum/SDQL_parser/proc/comparitor(i, list/node) +//comparator: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' +/datum/sdql_parser/proc/comparator(i, list/node) + if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">=")) node += token(i) else - parse_error("Unknown comparitor [token(i)]") + parse_error("Unknown comparator [token(i)]") return i + 1 -//bool_operator: 'AND' | '&&' | 'OR' | '||' -/datum/SDQL_parser/proc/bool_operator(i, list/node) +//bool_operator: 'AND' | '&&' | 'OR' | '||' +/datum/sdql_parser/proc/bool_operator(i, list/node) + if(tokenl(i) in list("and", "or", "&&", "||")) node += token(i) else - parse_error("Unknown comparitor [token(i)]") + parse_error("Unknown comparator [token(i)]") return i + 1 -//string: ''' ''' | '"' '"' -/datum/SDQL_parser/proc/string(i, list/node) - if(copytext(token(i), 1, 2) in list("'", "\"")) +//string: ''' ''' | '"' '"' +/datum/sdql_parser/proc/string(i, list/node) + + if(token(i)[1] in list("'", "\"")) node += token(i) else @@ -392,38 +457,111 @@ return i + 1 +//array: '[' expression_list ']' +/datum/sdql_parser/proc/array(i, list/node) + // Arrays get turned into this: list("[", list(exp_1a = exp_1b, ...), ...), "[" is to mark the next node as an array. + if(token(i)[1] != "\[") + parse_error("Expected an array but found '[token(i)]'") + return i + 1 -//call_function: ['(' [arguments] ')'] -/datum/SDQL_parser/proc/call_function(i, list/node) - parse_error("Sorry, function calls aren't available yet") + node += token(i) // Add the "[" - return i + var/list/expression_list = list() + + i++ + if(token(i) != "]") + var/list/temp_expression_list = list() + var/tok + do + tok = token(i) + if (tok == "," || tok == ":") + if (temp_expression_list == null) + parse_error("Found ',' or ':' without expression in an array.") + return i + 1 + + expression_list[++expression_list.len] = temp_expression_list + temp_expression_list = null + if (tok == ":") + temp_expression_list = list() + i = expression(i + 1, temp_expression_list) + expression_list[expression_list[expression_list.len]] = temp_expression_list + temp_expression_list = null + tok = token(i) + if (tok != ",") + if (tok == "]") + break + + parse_error("Expected ',' or ']' after array assoc value, but found '[token(i)]'") + return i -//select_function: count_function -/datum/SDQL_parser/proc/select_function(i, list/node) - parse_error("Sorry, function calls aren't available yet") + i++ + continue - return i + temp_expression_list = list() + i = expression(i, temp_expression_list) + while(token(i) && token(i) != "]") -//expression: ( unary_expression | '(' expression ')' | value ) [binary_operator expression] -/datum/SDQL_parser/proc/expression(i, list/node) - if(token(i) in unary_operators) - i = unary_expression(i, node) + if (temp_expression_list) + expression_list[++expression_list.len] = temp_expression_list - else if(token(i) == "(") - var/list/expr = list() + node[++node.len] = expression_list - i = expression(i + 1, expr) + return i + 1 - if(token(i) != ")") - parse_error("Missing ) at end of expression.") +//selectors_array: '@[' object_selectors ']' +/datum/sdql_parser/proc/selectors_array(i, list/node) + if(token(i) == "@\[") + node += token(i++) + if(token(i) != "]") + var/list/select = list() + i = object_selectors(i, select) + node[++node.len] = select + if(token(i) != "]") + parse_error("Expected ']' to close selector array, but found '[token(i)]'") + else + parse_error("Selector array expected a selector, but found nothing") + else + parse_error("Expected '@\[' but found '[token(i)]'") + return i + 1 + +//call_function: ['(' [arguments] ')'] +/datum/sdql_parser/proc/call_function(i, list/node, list/arguments) + if(length(tokenl(i))) + var/procname = "" + if(tokenl(i) == "global" && token(i + 1) == ".") // Global proc. + i += 2 + procname = "global." + node += procname + token(i++) + if(token(i) != "(") + parse_error("Expected ( but found '[token(i)]'") + + else if(token(i + 1) != ")") + var/list/temp_expression_list = list() + do + i = expression(i + 1, temp_expression_list) + if(token(i) == ",") + arguments[++arguments.len] = temp_expression_list + temp_expression_list = list() + continue + + while(token(i) && token(i) != ")") + + arguments[++arguments.len] = temp_expression_list // The code this is copy pasted from won't be executed when it's the last param, this fixes that. else i++ + else + parse_error("Expected a function but found nothing") + return i + 1 - node[++node.len] = expr + +//expression: ( unary_expression | value ) [binary_operator expression] +/datum/sdql_parser/proc/expression(i, list/node) + + if(token(i) in unary_operators) + i = unary_expression(i, node) else i = value(i, node) @@ -432,7 +570,7 @@ i = binary_operator(i, node) i = expression(i, node) - else if(token(i) in comparitors) + else if(token(i) in comparators) i = binary_operator(i, node) var/list/rhs = list() @@ -444,8 +582,9 @@ return i -//unary_expression: unary_operator ( unary_expression | value | '(' expression ')' ) -/datum/SDQL_parser/proc/unary_expression(i, list/node) +//unary_expression: unary_operator ( unary_expression | value ) +/datum/sdql_parser/proc/unary_expression(i, list/node) + if(token(i) in unary_operators) var/list/unary_exp = list() @@ -455,19 +594,6 @@ if(token(i) in unary_operators) i = unary_expression(i, unary_exp) - else if(token(i) == "(") - var/list/expr = list() - - i = expression(i + 1, expr) - - if(token(i) != ")") - parse_error("Missing ) at end of expression.") - - else - i++ - - unary_exp[++unary_exp.len] = expr - else i = value(i, unary_exp) @@ -480,9 +606,10 @@ return i -//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' -/datum/SDQL_parser/proc/binary_operator(i, list/node) - if(token(i) in (binary_operators + comparitors)) +//binary_operator: comparator | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%' +/datum/sdql_parser/proc/binary_operator(i, list/node) + + if(token(i) in (binary_operators + comparators)) node += token(i) else @@ -491,25 +618,36 @@ return i + 1 -//value: variable | string | number | 'null' -/datum/SDQL_parser/proc/value(i, list/node) +//value: variable | string | number | 'null' | object_type | array | selectors_array +/datum/sdql_parser/proc/value(i, list/node) if(token(i) == "null") node += "null" i++ + else if(lowertext(copytext(token(i), 1, 3)) == "0x" && isnum(hex2num(copytext(token(i), 3))))//3 == length("0x") + 1 + node += hex2num(copytext(token(i), 3)) + i++ + else if(isnum(text2num(token(i)))) node += text2num(token(i)) i++ - else if(copytext(token(i), 1, 2) in list("'", "\"")) + else if(token(i)[1] in list("'", "\"")) i = string(i, node) + else if(token(i)[1] == "\[") // Start a list. + i = array(i, node) + + else if(copytext(token(i), 1, 3) == "@\[")//3 == length("@\[") + 1 + i = selectors_array(i, node) + + else if(token(i)[1] == "/") + i = object_type(i, node) + else i = variable(i, node) return i - - - -/*EXPLAIN SELECT * WHERE 42 = 6 * 9 OR val = - 5 == 7*/ +#undef SDQL2_VALID_OPTION_TYPES +#undef SDQL2_VALID_OPTION_VALUES diff --git a/code/modules/admin/verbs/SDQL_2_wrappers.dm b/code/modules/admin/verbs/SDQL_2_wrappers.dm new file mode 100644 index 00000000000..ecb172aefdb --- /dev/null +++ b/code/modules/admin/verbs/SDQL_2_wrappers.dm @@ -0,0 +1,293 @@ +// Wrappers for BYOND default procs which can't directly be called by call(). + +/proc/_abs(A) + return abs(A) + +/proc/_animate(atom/target, set_vars, time = 10, loop = 1, easing = LINEAR_EASING, flags = null) + if(target) + animate(target, appearance = set_vars, time, loop, easing, flags) + else + animate(appearance = set_vars, time, easing = easing, flags) + +/proc/_arccos(A) + return arccos(A) + +/proc/_arcsin(A) + return arcsin(A) + +/proc/_ascii2text(A) + return ascii2text(A) + +/proc/_block(Start, End) + return block(Start, End) + +/proc/_ckey(Key) + return ckey(Key) + +/proc/_ckeyEx(Key) + return ckeyEx(Key) + +/proc/_copytext(T, Start = 1, End = 0) + return copytext(T, Start, End) + +/proc/_cos(X) + return cos(X) + +/proc/_findtext(Haystack, Needle, Start = 1, End = 0) + return findtext(Haystack, Needle, Start, End) + +/proc/_findtextEx(Haystack, Needle, Start = 1, End = 0) + return findtextEx(Haystack, Needle, Start, End) + +/proc/_flick(Icon, Object) + flick(Icon, Object) + +/proc/_get_dir(Loc1, Loc2) + return get_dir(Loc1, Loc2) + +/proc/_get_dist(Loc1, Loc2) + return get_dist(Loc1, Loc2) + +/proc/_get_step(Ref, Dir) + return get_step(Ref, Dir) + +/proc/_hearers(Depth = world.view, Center = usr) + return hearers(Depth, Center) + +/proc/_image(icon, loc, icon_state, layer, dir) + return image(icon, loc, icon_state, layer, dir) + +/proc/_istype(object, type) + return istype(object, type) + +/proc/_ispath(path, type) + return ispath(path, type) + +/proc/_length(E) + return length(E) + +/proc/_link(thing, url) + thing << link(url) + +/proc/_locate(X, Y, Z) + if (isnull(Y)) // Assuming that it's only a single-argument call. + // direct ref locate + var/datum/D = locate(X) + // &&'s to last value + return istype(D) && D + + return locate(X, Y, Z) + +/proc/_log(X, Y) + return log(X, Y) + +/proc/_lowertext(T) + return lowertext(T) + +/proc/_matrix(a, b, c, d, e, f) + return matrix(a, b, c, d, e, f) + +/proc/_max(...) + return max(arglist(args)) + +/proc/_md5(T) + return md5(T) + +/proc/_min(...) + return min(arglist(args)) + +/proc/_new(type, arguments) + var/datum/result + + if(!length(arguments)) + result = new type() + else + result = new type(arglist(arguments)) + return result + +/proc/_num2text(N, SigFig = 6) + return num2text(N, SigFig) + +/proc/_text2num(T) + return text2num(T) + +/proc/_ohearers(Dist, Center = usr) + return ohearers(Dist, Center) + +/proc/_orange(Dist, Center = usr) + return orange(Dist, Center) + +/proc/_output(thing, msg, control) + thing << output(msg, control) + +/proc/_oview(Dist, Center = usr) + return oview(Dist, Center) + +/proc/_oviewers(Dist, Center = usr) + return oviewers(Dist, Center) + +/proc/_params2list(Params) + return params2list(Params) + +/proc/_pick(...) + return pick(arglist(args)) + +/// Allow me to explain +/// for some reason, if pick() is passed arglist(args) directly and args contains only one list +/// it considers it to be a list of lists +/// this means something like _pick(list) would fail +/// need to do this instead +/// +/// I hate this timeline +/proc/_pick_list(list/pick_from) + return pick(pick_from) + +/proc/_prob(P) + return prob(P) + +/proc/_rand(L = 0, H = 1) + return rand(L, H) + +/proc/_range(Dist, Center = usr) + return range(Dist, Center) + +/proc/_rect_turfs(H_Radius = 0, V_Radius = 0, atom/Center) + return RECT_TURFS(H_Radius, V_Radius, Center) + +/proc/_regex(pattern, flags) + return regex(pattern, flags) + +/proc/_REGEX_QUOTE(text) + return REGEX_QUOTE(text) + +/proc/_REGEX_QUOTE_REPLACEMENT(text) + return REGEX_QUOTE_REPLACEMENT(text) + +/proc/_replacetext(Haystack, Needle, Replacement, Start = 1,End = 0) + return replacetext(Haystack, Needle, Replacement, Start, End) + +/proc/_replacetextEx(Haystack, Needle, Replacement, Start = 1,End = 0) + return replacetextEx(Haystack, Needle, Replacement, Start, End) + +/proc/_rgb(R, G, B) + return rgb(R, G, B) + +/proc/_rgba(R, G, B, A) + return rgb(R, G, B, A) + +/proc/_roll(dice) + return roll(dice) + +/proc/_round(A, B = 1) + return round(A, B) + +/proc/_sin(X) + return sin(X) + +/proc/_list_add(list/L, ...) + if (args.len < 2) + return + L += args.Copy(2) + +/proc/_list_copy(list/L, Start = 1, End = 0) + return L.Copy(Start, End) + +/proc/_list_cut(list/L, Start = 1, End = 0) + L.Cut(Start, End) + +/proc/_list_find(list/L, Elem, Start = 1, End = 0) + return L.Find(Elem, Start, End) + +/proc/_list_insert(list/L, Index, Item) + return L.Insert(Index, Item) + +/proc/_list_join(list/L, Glue, Start = 0, End = 1) + return L.Join(Glue, Start, End) + +/proc/_list_remove(list/L, ...) + if (args.len < 2) + return + L -= args.Copy(2) + +/proc/_list_set(list/L, key, value) + L[key] = value + +/proc/_list_get(list/L, key) + return L[key] + +/proc/_list_numerical_add(L, key, num) + L[key] += num + +/proc/_list_swap(list/L, Index1, Index2) + L.Swap(Index1, Index2) + +/proc/_walk(ref, dir, lag) + walk(ref, dir, lag) + +/proc/_walk_towards(ref, trg, lag) + walk_towards(ref, trg, lag) + +/proc/_walk_to(ref, trg, min, lag) + walk_to(ref, trg, min, lag) + +/proc/_walk_away(ref, trg, max, lag) + walk_away(ref, trg, max, lag) + +/proc/_walk_rand(ref, lag) + walk_rand(ref, lag) + +/proc/_step(ref, dir) + step(ref, dir) + +/proc/_step_rand(ref) + step_rand(ref) + +/proc/_step_to(ref, trg, min) + step_to(ref, trg, min) + +/proc/_step_towards(ref, trg) + step_towards(ref, trg) + +/proc/_step_away(ref, trg, max) + step_away(ref, trg, max) + +/proc/_has_trait(datum/thing, trait) + return HAS_TRAIT(thing, trait) + +/proc/_add_trait(datum/thing, trait, source) + ADD_TRAIT(thing, trait, source) + +/proc/_remove_trait(datum/thing, trait, source) + REMOVE_TRAIT(thing, trait, source) + +/proc/_winset(player, control_id, params) + winset(player, control_id, params) + +/proc/_winget(player, control_id, params) + winget(player, control_id, params) + +/proc/_text2path(text) + return text2path(text) + +/proc/_turn(dir, angle) + return turn(dir, angle) + +/proc/_view(Dist, Center = usr) + return view(Dist, Center) + +/proc/_viewers(Dist, Center = usr) + return viewers(Dist, Center) + +/// 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. +/// For example, calling some_proc([a filter], foo, bar) from auxtools +/// is equivalent to calling some_proc(foo, bar). Thus, we can't use _animate directly on filters. +/// Use this to perform animation steps on a filter. Consecutive steps on the same filter can be +/// achieved by calling _animate with no target. +/proc/_animate_filter(atom/target, filter_index, set_vars, time = 10, loop = 1, easing = LINEAR_EASING, flags = null) + if(!istype(target)) + return + if(!filter_index || filter_index < 1 || filter_index > length(target.filters)) + return + animate(target.filters[filter_index], appearance = set_vars, time, loop, easing, flags) diff --git a/code/modules/admin/verbs/massmodvar.dm b/code/modules/admin/verbs/massmodvar.dm index 206ce78364a..c6a90796b0e 100644 --- a/code/modules/admin/verbs/massmodvar.dm +++ b/code/modules/admin/verbs/massmodvar.dm @@ -28,11 +28,6 @@ var/list/locked = list("vars", "key", "ckey", "client") - for(var/p in forbidden_varedit_object_types) - if( istype(O,p) ) - to_chat(usr, "It is forbidden to edit this object's variables.") - return - var/list/names = list() for (var/V in O.vars) names += V @@ -46,7 +41,8 @@ else variable = var_name - if(!variable) return + if(!variable) + return var/default var/var_value = O.vars[variable] var/dir @@ -129,9 +125,12 @@ original_name = O:name switch(class) - if("restore to default") - O.vars[variable] = initial(O.vars[variable]) + var/initial_var = initial(O.vars[variable]) + if(!O.vv_edit_var(variable, initial_var)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = initial_var if(method) if(istype(O, /mob)) for(var/mob/M in mob_list) @@ -169,7 +168,11 @@ if("text") var/new_value = input("Enter new text:","Text",O.vars[variable]) as text|null//todo: sanitize ??? - if(new_value == null) return + if(new_value == null) + return + if(!O.vv_edit_var(variable, new_value)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return O.vars[variable] = new_value if(method) @@ -206,7 +209,12 @@ if("num") var/new_value = input("Enter new number:","Num",\ O.vars[variable]) as num|null - if(new_value == null) return + if(new_value == null) + return + + if(!O.vv_edit_var(variable, new_value)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return if(variable=="light_range") O.set_light(new_value) @@ -266,7 +274,13 @@ if("type") var/new_value new_value = input("Enter type:","Type",O.vars[variable]) as null|anything in typesof(/obj,/mob,/area,/turf) - if(new_value == null) return + if(new_value == null) + return + + if(!O.vv_edit_var(variable, new_value)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = new_value if(method) if(istype(O, /mob)) @@ -301,7 +315,13 @@ if("file") var/new_value = input("Pick file:","File",O.vars[variable]) as null|file - if(new_value == null) return + if(new_value == null) + return + + if(!O.vv_edit_var(variable, new_value)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = new_value if(method) @@ -337,7 +357,13 @@ if("icon") var/new_value = input("Pick icon:","Icon",O.vars[variable]) as null|icon - if(new_value == null) return + if(new_value == null) + return + + if(!O.vv_edit_var(variable, new_value)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = new_value if(method) if(istype(O, /mob)) diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 153820d42a6..93453297e81 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -1,10 +1,3 @@ -var/list/forbidden_varedit_object_types = list( - /datum/admins, //Admins editing their own admin-power object? Yup, sounds like a good idea. - /datum/controller/subsystem/statistics, //Prevents people messing with feedback gathering - /datum/controller/subsystem/discord, //Nope.jpg - /datum/feedback_variable //Prevents people messing with feedback gathering -) - var/list/VVlocked = list("vars", "holder", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "bound_x", "bound_y", "step_x", "step_y", "force_ending") var/list/VVicon_edit_lock = list("icon", "icon_state", "overlays", "underlays") var/list/VVckey_edit = list("key", "ckey") @@ -406,15 +399,14 @@ var/list/VVdynamic_lock = list( /client/proc/modify_variables(var/atom/O, var/param_var_name = null, var/autodetect_class = 0) if(!check_rights(R_VAREDIT|R_DEV)) return - for(var/p in forbidden_varedit_object_types) - if( istype(O,p) ) - to_chat(usr, "It is forbidden to edit this object's variables.") - return - if(istype(O, /client) && (param_var_name == "ckey" || param_var_name == "key")) to_chat(usr, "You cannot edit ckeys on client objects.") return + if(!O.vv_edit_var(param_var_name)) + to_chat(src, SPAN_WARNING("You cannot edit this variable.")) + return + var/class var/variable var/var_value @@ -485,7 +477,8 @@ var/list/VVdynamic_lock = list( names = sortList(names) variable = input("Which var?","Var") as null|anything in names - if(!variable) return + if(!variable) + return var_value = O.vars[variable] if(variable in VVlocked) @@ -596,17 +589,35 @@ var/list/VVdynamic_lock = list( if("text") var/var_new = input("Enter new text:","Text",O.vars[variable]) as null|text - if(var_new==null) return + if(var_new==null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("num") if(variable=="light_range") var/var_new = input("Enter new number:","Num",O.vars[variable]) as null|num - if(var_new == null) return + if(var_new == null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.set_light(var_new) else if(variable=="stat") var/var_new = input("Enter new number:","Num",O.vars[variable]) as null|num - if(var_new == null) return + if(var_new == null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + if((O.vars[variable] == 2) && (var_new < 2))//Bringing the dead back to life dead_mob_list -= O living_mob_list += O @@ -616,7 +627,13 @@ var/list/VVdynamic_lock = list( O.vars[variable] = var_new else var/var_new = input("Enter new number:","Num",O.vars[variable]) as null|num - if(var_new==null) return + if(var_new==null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("type") @@ -641,27 +658,56 @@ var/list/VVdynamic_lock = list( else var_new = input("Select an atom type", "Spawn Atom", matches[1]) as null|anything in matches - if(var_new==null) return + if(var_new==null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("reference") var/var_new = input("Select reference:","Reference",O.vars[variable]) as null|mob|obj|turf|area in world - if(var_new==null) return + if(var_new==null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("mob reference") var/var_new = input("Select reference:","Reference",O.vars[variable]) as null|mob in world if(var_new==null) return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("file") var/var_new = input("Pick file:","File",O.vars[variable]) as null|file - if(var_new==null) return + if(var_new==null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("icon") var/var_new = input("Pick icon:","Icon",O.vars[variable]) as null|icon - if(var_new==null) return + if(var_new==null) + return + + if(!O.vv_edit_var(variable, var_new)) + to_chat(usr, SPAN_WARNING("You cannot edit this variable.")) + return + O.vars[variable] = var_new if("marked datum") diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 5fd3f114e75..42217ca8a99 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -21,7 +21,7 @@ if(!D) return - var/static/list/blacklist = list(/datum/configuration, /datum/controller/subsystem/discord) + var/static/list/blacklist = list(/datum/configuration) if(is_type_in_list(D,blacklist)) return @@ -121,6 +121,8 @@ CHECK_TICK if(x in view_variables_hide_vars) continue + if(!D.can_vv_get(x)) + continue variables += x variables = sortList(variables) for(var/x in variables) diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm index 16b6f066472..8489d7292e1 100644 --- a/code/modules/asset_cache/asset_cache_item.dm +++ b/code/modules/asset_cache/asset_cache_item.dm @@ -47,8 +47,5 @@ ext = ".[copytext(name, extstart+1)]" resource = file -// /datum/asset_cache_item/vv_edit_var(var_name, var_value) -// return FALSE - -// /datum/asset_cache_item/CanProcCall(procname) -// return FALSE +/datum/asset_cache_item/vv_edit_var(var_name, var_value) + return FALSE diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index e71a35e6665..98ef57c8143 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -510,7 +510,7 @@ data["charge"] = cell ? round(cell.charge,1) : 0 data["maxcharge"] = cell ? cell.maxcharge : 0 - data["chargestatus"] = cell ? Floor((cell.charge/cell.maxcharge)*50) : 0 + data["chargestatus"] = cell ? FLOOR((cell.charge/cell.maxcharge)*50) : 0 data["emagged"] = subverted data["coverlock"] = locked diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm index 2de43c3ea76..7b70c61e270 100644 --- a/code/modules/games/cards.dm +++ b/code/modules/games/cards.dm @@ -304,7 +304,7 @@ add_overlay(I) return - var/offset = Floor(20/cards.len) + var/offset = FLOOR(20/cards.len) var/matrix/M = matrix() if(direction) diff --git a/code/modules/mob/language/language.dm b/code/modules/mob/language/language.dm index 60321cb0aff..f72350d71fc 100644 --- a/code/modules/mob/language/language.dm +++ b/code/modules/mob/language/language.dm @@ -41,7 +41,7 @@ for(var/i = 0;i0;x--) + for(var/x = rand(FLOOR(syllable_count/syllable_divisor),syllable_count);x>0;x--) new_name += pick(syllables) full_name += " [capitalize(lowertext(new_name))]" diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 4def206ee74..22c27cc3565 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -392,7 +392,7 @@ to_chat(src, SPAN_WARNING("You slipped on [slipped_on]!")) playsound(src.loc, 'sound/misc/slip.ogg', 50, 1, -3) Stun(stun_duration) - Weaken(Floor(stun_duration/2)) + Weaken(FLOOR(stun_duration/2)) return 1 /mob/living/carbon/proc/add_chemical_effect(var/effect, var/magnitude = 1) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index ec454a34b30..461f85c8af9 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1918,9 +1918,9 @@ //Get fluffy numbers /mob/living/carbon/human/proc/blood_pressure() if(status_flags & FAKEDEATH) - return list(Floor(species.bp_base_systolic+rand(-5,5))*0.25, Floor(species.bp_base_disatolic+rand(-5,5))*0.25) + return list(FLOOR(species.bp_base_systolic+rand(-5,5))*0.25, FLOOR(species.bp_base_disatolic+rand(-5,5))*0.25) var/blood_result = get_blood_circulation() - return list(Floor((species.bp_base_systolic+rand(-5,5))*(blood_result/100)), Floor((species.bp_base_disatolic+rand(-5,5))*(blood_result/100))) + return list(FLOOR((species.bp_base_systolic+rand(-5,5))*(blood_result/100)), FLOOR((species.bp_base_disatolic+rand(-5,5))*(blood_result/100))) //Formats blood pressure for text display /mob/living/carbon/human/proc/get_blood_pressure() diff --git a/code/modules/mob/living/maneuvers/_maneuver.dm b/code/modules/mob/living/maneuvers/_maneuver.dm index 56605896da6..0b673b5a5d2 100644 --- a/code/modules/mob/living/maneuvers/_maneuver.dm +++ b/code/modules/mob/living/maneuvers/_maneuver.dm @@ -23,7 +23,7 @@ return FALSE if(world.time < user.last_special) if(!silent) - to_chat(user, SPAN_WARNING("You cannot maneuver again for another [Floor((user.last_special - world.time)*0.1)] second\s.")) + to_chat(user, SPAN_WARNING("You cannot maneuver again for another [FLOOR((user.last_special - world.time)*0.1)] second\s.")) return FALSE if(!isipc(user)) if(user.stamina < stamina_cost) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index de288219968..3dfaf7da9a0 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -825,7 +825,7 @@ //Robots take half damage from basic attacks. /mob/living/silicon/robot/attack_generic(var/mob/user, var/damage, var/attack_message) - return ..(user,Floor(damage/2),attack_message) + return ..(user,FLOOR(damage/2),attack_message) /mob/living/silicon/robot/proc/allowed(mob/M) // Check if the borg doesn't require any access at all diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 96f1e24d3ba..d7a6d76de50 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -464,7 +464,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp return M.shakecamera = current_time + max(TICKS_PER_RECOIL_ANIM, duration) strength = abs(strength)*PIXELS_PER_STRENGTH_VAL - var/steps = min(1, Floor(duration/TICKS_PER_RECOIL_ANIM))-1 + var/steps = min(1, FLOOR(duration/TICKS_PER_RECOIL_ANIM))-1 animate(M.client, pixel_x = rand(-(strength), strength), pixel_y = rand(-(strength), strength), time = TICKS_PER_RECOIL_ANIM, easing = JUMP_EASING|EASE_IN) if(steps) for(var/i = 1 to steps) diff --git a/code/modules/organs/internal/_internal.dm b/code/modules/organs/internal/_internal.dm index 41c30508a40..dfa1078ac4c 100644 --- a/code/modules/organs/internal/_internal.dm +++ b/code/modules/organs/internal/_internal.dm @@ -48,7 +48,7 @@ if(damage > min_broken_damage) var/scarring = damage / max_damage scarring = 1 - 0.5 * scarring ** 2 // Between ~15 and 50 percent loss. - var/new_max_dam = Floor(scarring * max_damage) + var/new_max_dam = FLOOR(scarring * max_damage) if(new_max_dam < max_damage) to_chat(user, SPAN_WARNING("Not every part of [src] could be saved; some dead tissue had to be removed, making it more susceptible to future damage.")) set_max_damage(new_max_dam) @@ -97,9 +97,9 @@ return damage /obj/item/organ/internal/proc/set_max_damage(var/ndamage) - max_damage = Floor(ndamage) - min_broken_damage = Floor(0.75 * max_damage) - min_bruised_damage = Floor(0.25 * max_damage) + max_damage = FLOOR(ndamage) + min_broken_damage = FLOOR(0.75 * max_damage) + min_bruised_damage = FLOOR(0.25 * max_damage) /obj/item/organ/internal/proc/take_internal_damage(amount, var/silent=0) if(BP_IS_ROBOTIC(src)) diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm index f04b9db8ed3..0face7ab338 100644 --- a/code/modules/organs/internal/brain.dm +++ b/code/modules/organs/internal/brain.dm @@ -183,7 +183,7 @@ var/blood_volume = owner.get_blood_oxygenation() if(blood_volume < BLOOD_VOLUME_BAD) to_chat(user, "Parts of [src] didn't survive the procedure due to lack of air supply!") - set_max_damage(Floor(max_damage - 0.25*damage)) + set_max_damage(FLOOR(max_damage - 0.25*damage)) heal_damage(damage) /obj/item/organ/internal/brain/get_scarring_level() diff --git a/code/modules/organs/internal/heart.dm b/code/modules/organs/internal/heart.dm index 08c8d364c38..54614119a80 100644 --- a/code/modules/organs/internal/heart.dm +++ b/code/modules/organs/internal/heart.dm @@ -146,7 +146,7 @@ blood_max += ((W.damage / 40) * species.bleed_mod) if(temp.status & ORGAN_ARTERY_CUT) - var/bleed_amount = Floor((owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 450 : 250)) * temp.arterial_bleed_severity) + var/bleed_amount = FLOOR((owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 450 : 250)) * temp.arterial_bleed_severity) if(bleed_amount) if(CE_BLOODCLOT in owner.chem_effects) bleed_amount *= 0.8 // won't do much, but it'll help diff --git a/code/modules/organs/internal/stomach.dm b/code/modules/organs/internal/stomach.dm index cbd7fedbde0..2731745b6a0 100644 --- a/code/modules/organs/internal/stomach.dm +++ b/code/modules/organs/internal/stomach.dm @@ -54,7 +54,7 @@ return !isnull(get_devour_time(food)) /obj/item/organ/internal/stomach/proc/is_full(var/atom/movable/food) - var/total = Floor(ingested.total_volume / 10) + var/total = FLOOR(ingested.total_volume / 10) if(species.gluttonous & GLUT_MESSY) return FALSE //Don't need to check if the stomach is full if we're not using the contents. for(var/a in contents + food) diff --git a/code/modules/overmap/ships/ship.dm b/code/modules/overmap/ships/ship.dm index 656aa2831e7..674b3f92b49 100644 --- a/code/modules/overmap/ships/ship.dm +++ b/code/modules/overmap/ships/ship.dm @@ -206,7 +206,7 @@ var/const/OVERMAP_SPEED_CONSTANT = (1 SECOND) if(position[i] < 0) deltas[i] = CEILING(position[i], 1) else if(position[i] > 0) - deltas[i] = Floor(position[i]) + deltas[i] = FLOOR(position[i]) if(deltas[i] != 0) position[i] -= deltas[i] position[i] += (deltas[i] > 0) ? -1 : 1 diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/power/fusion/core/core_field.dm index ed95b4b99f3..8c8d3ff74e5 100644 --- a/code/modules/power/fusion/core/core_field.dm +++ b/code/modules/power/fusion/core/core_field.dm @@ -424,7 +424,7 @@ /obj/effect/fusion_em_field/proc/Radiate() if(istype(loc, /turf)) - for(var/atom/movable/AM in range(max(1,Floor(size/2)), loc)) + for(var/atom/movable/AM in range(max(1,FLOOR(size/2)), loc)) if(AM == src || AM == owned_core || !AM.simulated) continue @@ -469,7 +469,7 @@ //determine a random amount to actually react this cycle, and remove it from the standard pool //this is a hack, and quite nonrealistic :( for(var/reactant in react_pool) - react_pool[reactant] = rand(Floor(react_pool[reactant]/2),react_pool[reactant]) + react_pool[reactant] = rand(FLOOR(react_pool[reactant]/2),react_pool[reactant]) reactants[reactant] -= react_pool[reactant] if(!react_pool[reactant]) react_pool -= reactant diff --git a/code/modules/power/fusion/kinetic_harvester.dm b/code/modules/power/fusion/kinetic_harvester.dm index ad267b3ddae..0b458fab3b2 100644 --- a/code/modules/power/fusion/kinetic_harvester.dm +++ b/code/modules/power/fusion/kinetic_harvester.dm @@ -69,7 +69,7 @@ for(var/mat in stored) var/material/material = SSmaterials.get_material_by_name(mat) if(material) - var/sheets = Floor(stored[mat]/(SHEET_MATERIAL_AMOUNT * 1.5)) + var/sheets = FLOOR(stored[mat]/(SHEET_MATERIAL_AMOUNT * 1.5)) data["materials"] += list(list("material" = mat, "rawamount" = stored[mat], "amount" = sheets, "harvest" = harvesting[mat])) return data @@ -112,7 +112,7 @@ var/material/material = SSmaterials.get_material_by_name(mat) if(material) var/sheet_cost = (SHEET_MATERIAL_AMOUNT * 1.5) - var/sheets = Floor(stored[mat]/sheet_cost) + var/sheets = FLOOR(stored[mat]/sheet_cost) if(sheets > 0) var/obj/item/stack/material/M = new material.stack_type(get_turf(src), sheets) M.update_icon() diff --git a/code/modules/power/portgen.dm b/code/modules/power/portgen.dm index ca9cbb19a89..661a8e80e74 100644 --- a/code/modules/power/portgen.dm +++ b/code/modules/power/portgen.dm @@ -330,7 +330,7 @@ if(loc) var/datum/gas_mixture/environment = loc.return_air() if(environment) - data["temperature_min"] = Floor(environment.temperature - T0C) + data["temperature_min"] = FLOOR(environment.temperature - T0C) data["output_min"] = initial(power_output) data["is_broken"] = IsBroken() diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 8cd4841d189..b37c1eff3db 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -1,6 +1,6 @@ #define I_SINGULO "singulo" -/obj/singularity/ +/obj/singularity name = "gravitational singularity" desc = "A gravitational singularity." icon = 'icons/obj/singularity.dmi' diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index e28063c705f..2534b9ca7c6 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -577,12 +577,12 @@ time_offset = 0 var/required_moves = 0 if(speed > 0) - required_moves = Floor(elapsed_time_deciseconds / speed, 1) + required_moves = FLOOR_FLOAT(elapsed_time_deciseconds / speed, 1) if(required_moves > SSprojectiles.global_max_tick_moves) var/overrun = required_moves - SSprojectiles.global_max_tick_moves required_moves = SSprojectiles.global_max_tick_moves time_offset += overrun * speed - time_offset += Modulus(elapsed_time_deciseconds, speed) + time_offset += MODULUS(elapsed_time_deciseconds, speed) else required_moves = SSprojectiles.global_max_tick_moves if(!required_moves) diff --git a/code/modules/reagents/reagent_containers/food/snacks/fish.dm b/code/modules/reagents/reagent_containers/food/snacks/fish.dm index 8b440b24823..41366acc16e 100644 --- a/code/modules/reagents/reagent_containers/food/snacks/fish.dm +++ b/code/modules/reagents/reagent_containers/food/snacks/fish.dm @@ -8,7 +8,7 @@ /obj/item/reagent_containers/food/snacks/fish/attackby(var/obj/item/W, var/mob/user) if(is_sharp(W) && (locate(/obj/structure/table) in loc)) - var/transfer_amt = Floor(reagents.total_volume/3) + var/transfer_amt = FLOOR(reagents.total_volume/3) for(var/i = 1 to 3) var/obj/item/reagent_containers/food/snacks/sashimi/sashimi = new(get_turf(src), fish_type) reagents.trans_to(sashimi, transfer_amt) diff --git a/code/modules/reagents/reagent_containers/food/snacks/meat.dm b/code/modules/reagents/reagent_containers/food/snacks/meat.dm index 42028f43e04..0451f16e3cd 100644 --- a/code/modules/reagents/reagent_containers/food/snacks/meat.dm +++ b/code/modules/reagents/reagent_containers/food/snacks/meat.dm @@ -361,7 +361,7 @@ /obj/item/reagent_containers/food/snacks/squidmeat/attackby(var/obj/item/W, var/mob/user) if(is_sharp(W) && (locate(/obj/structure/table) in loc)) - var/transfer_amt = Floor(reagents.total_volume/3) + var/transfer_amt = FLOOR(reagents.total_volume/3) for(var/i = 1 to 3) var/obj/item/reagent_containers/food/snacks/sashimi/sashimi = new(get_turf(src), "squid") reagents.trans_to(sashimi, transfer_amt) diff --git a/code/modules/turbolift/turbolift_map.dm b/code/modules/turbolift/turbolift_map.dm index ad2b5a61aec..fe0d2aa26d9 100644 --- a/code/modules/turbolift/turbolift_map.dm +++ b/code/modules/turbolift/turbolift_map.dm @@ -53,7 +53,7 @@ if(NORTH) - int_panel_x = ux + Floor(lift_size_x/2) + int_panel_x = ux + FLOOR(lift_size_x/2) int_panel_y = uy + 1 ext_panel_x = ux ext_panel_y = ey + 2 @@ -70,7 +70,7 @@ if(SOUTH) - int_panel_x = ux + Floor(lift_size_x/2) + int_panel_x = ux + FLOOR(lift_size_x/2) int_panel_y = ey - 1 ext_panel_x = ex ext_panel_y = uy - 2 @@ -88,7 +88,7 @@ if(EAST) int_panel_x = ux+1 - int_panel_y = uy + Floor(lift_size_y/2) + int_panel_y = uy + FLOOR(lift_size_y/2) ext_panel_x = ex+2 ext_panel_y = ey @@ -105,7 +105,7 @@ if(WEST) int_panel_x = ex-1 - int_panel_y = uy + Floor(lift_size_y/2) + int_panel_y = uy + FLOOR(lift_size_y/2) ext_panel_x = ux-2 ext_panel_y = uy diff --git a/html/changelogs/mattatlas-sdql2.yml b/html/changelogs/mattatlas-sdql2.yml new file mode 100644 index 00000000000..640874580c3 --- /dev/null +++ b/html/changelogs/mattatlas-sdql2.yml @@ -0,0 +1,43 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: MattAtlas + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - admin: "SDQL2 has now been updated to work properly, be faster, and overall better and more useful." + - admin: "View variables can no longer view some protected datums, such as the config datum." + - tweak: "Changed the Set Holidays verb to not have a . in front of the name."