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."
]